为了能到远方,脚下的每一步都不能少.|

玩单机的零度

园龄:2年2个月粉丝:1关注:8

2023-02-03 13:59阅读: 1915评论: 0推荐: 0

使用itext7进行pdf签名印章

🧀前言

  1. 需要准备ca证书的.keystore文件,测试的话一般可以用jdk进行生成

    使用cmd命令切换到jdk安装路径bin目录下,输入命令
    keytool -genkeypair -alias xh_https -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore C:/Users/86185/Desktop/html-pdf/resource/xh_https.keystore -storepass 123456
    alias是别名,keypass与storepass是密码,keyalg是密钥算法,keysize是密钥大小,validity是有效期,-keystore是路径加名称
    回车后会要求输入姓名、组织单位、城市区域等,可以自定义输入,输入完成后则在-keystore指定位置生成.keystore文件
  2. 相关依赖导入pom文件
    <!-- itext依赖 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>sign</artifactId>
        <version>7.1.16</version>
    </dependency>
    <!-- 加密软件包 -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.69</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.69</version>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>

一、签名实现类

package cn.cxyfyf.base.test.pdf.itext.config;

import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.source.RASInputStream;
import com.itextpdf.io.source.RandomAccessFileOrArray;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.layout.element.Image;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * pdf 签名工具类
 * @author fyf
 * Create by 2023/1/28 11:30
 */

public class SignUtils {

    private static final PdfSignDTO DEFAULT_SIGN_INFO = new PdfSignDTO();
    private static String KEYSTORE = "C:\\Users\\86185\\Desktop\\html-pdf\\resource\\tomcat_https.keystore";// keystore文件路径
    private static final char[] PASSWORD = "123456".toCharArray();// keystore密码

    static {
        DEFAULT_SIGN_INFO.setReason("系统自动签章");
        DEFAULT_SIGN_INFO.setLocation("Windows");
        DEFAULT_SIGN_INFO.setContactInfo("123456");
        DEFAULT_SIGN_INFO.setSignImgPath("C:\\Users\\86185\\Desktop\\html-pdf\\resource\\sign.png"); //印章图片路径
    }

    /**
     * 电子签章
     * @param inputStream 需要签章的pdf文件输入流
     * @param outputStream 签完章的pdf文件输出流
     */
    public static void signPdf(InputStream inputStream, OutputStream outputStream){
        signPdf(inputStream, outputStream, DEFAULT_SIGN_INFO);
    }

    /**
     * 电子签章
     * @param inputStream 需要签章的pdf文件输入流
     * @param outputStream 签完章的pdf文件输出流
     * @param pdfSignDTO 签名信息封装类
     */
    public static void signPdf(InputStream inputStream, OutputStream outputStream, PdfSignDTO pdfSignDTO)  {
        try (PdfReader reader = new PdfReader(inputStream);){
            //读取keystore ,获得私钥和证书链 jks
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream(KEYSTORE), PASSWORD);
            String alias = ks.aliases().nextElement();
            PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
            Certificate[] certificates = ks.getCertificateChain(alias);

            //创建签章工具PdfSigner、设定数字签章的属性
            PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties());
            PdfSignatureAppearance appearance = signer.getSignatureAppearance();

            // 读取文档相关属性
            PdfDocument document = signer.getDocument();

            appearance.setReason(pdfSignDTO.getReason());
            appearance.setLocation(pdfSignDTO.getLocation());
            appearance.setContact(pdfSignDTO.getContactInfo());
            //加盖图章图片
            ImageData img = ImageDataFactory.create(pdfSignDTO.getSignImgPath());
            Image image = new Image(img);
            appearance.setPageNumber(document.getNumberOfPages()); // 签名放到第几页
            appearance.setPageRect(new Rectangle(document.getLastPage().getPageSize().getWidth() - image.getImageWidth() - 50, 100, image.getImageWidth(), image.getImageHeight()));
            appearance.setSignatureGraphic(img);
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
            //No such provider: BC : 问题解决,加BC库支持
            Security.addProvider(new BouncyCastleProvider());
            //摘要算法
            IExternalDigest digest = new BouncyCastleDigest();
            //签名算法
            IExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, BouncyCastleProvider.PROVIDER_NAME);
            //调用itext签名方法完成pdf签章
            signer.setCertificationLevel(1); // 设置认证级别
            signer.signDetached(digest,signature, certificates, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 验签pdf
     * @param bytes 签名好的pdf字节数组
     * @return 验签结果 true/false
     */
    public static boolean verifyPdf(byte[] bytes){
        Objects.requireNonNull(bytes);
        return verifyPdf(new ByteArrayInputStream(bytes));
    }

    /**
     * 验签pdf
     * @param inputStream 签名好的pdf输入流
     * @return 验签结果 true/false
     */
    public static boolean verifyPdf(InputStream inputStream) {
        Objects.requireNonNull(inputStream);
        boolean result = false;
        try(PdfReader pdfReader = new PdfReader(inputStream);PdfDocument pdfDocument = new PdfDocument(pdfReader);) {

            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
            List<String> signedNames = signatureUtil.getSignatureNames();

            //遍历签名的内容并做验签
            for (String signedName : signedNames) {

                //获取源数据
                byte[] originData = getOriginData(pdfReader, signatureUtil, signedName);
                //获取签名值
                byte[] signedData = getSignData(signatureUtil , signedName);
                //校验签名
//                result = SignUtil.verifyP7DetachData(originData , signedData);
                if (!result) break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 获取签名数据
     * @param signatureUtil itextpdf signatureUtil
     * @param signedName itextpdf signedName
     */
    private static byte[] getSignData(SignatureUtil signatureUtil, String signedName) {
        PdfDictionary pdfDictionary = signatureUtil.getSignatureDictionary(signedName);
        PdfString contents = pdfDictionary.getAsString(PdfName.Contents);
        return contents.getValueBytes();
    }

    /**
     * 获取源数据(如果subFilter使用的是Adbe.pkcs7.detached就需要在验签的时候获取 源数据 并与 签名数据 进行 p7detach 校验)
     * @param pdfReader itextpdf pdfReader
     * @param signatureUtil itextpdf signatureUtil
     * @param signedName itextpdf signedName
     */
    private static byte[] getOriginData(PdfReader pdfReader, SignatureUtil signatureUtil, String signedName) {

        byte[] originData = null;
        PdfSignature pdfSignature = signatureUtil.getSignature(signedName);
        PdfArray pdfArray = pdfSignature.getByteRange();
        RandomAccessFileOrArray randomAccessFileOrArray = pdfReader.getSafeFile();
        try(InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(randomAccessFileOrArray.createSourceView(), SignatureUtil.asLongArray(pdfArray)));
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
            byte[] buf = new byte[8192];
            int n = 0;
            while (-1 != (n = rg.read(buf))) {
                outputStream.write(buf, 0, n);
            }
            originData = outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return originData;

    }

    /**
     * 解析返回签名信息
     * @param buf pdf字节数组
     * @return 签名信息列表
     */

    public static List<PdfSignDTO> getPdfSignInfo(byte[] buf) {
        Objects.requireNonNull(buf);
        return getPdfSignInfo(new ByteArrayInputStream(buf));
    }

    /**
     * 解析返回签名信息
     * @param inputStream pdf输入流
     * @return 签名信息列表
     */

    public static List<PdfSignDTO> getPdfSignInfo(InputStream inputStream) {

        //添加BC库支持
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        List<PdfSignDTO> signInfoList = new ArrayList< >();

        try(PdfReader pdfReader = new PdfReader(inputStream);
            PdfDocument pdfDocument = new PdfDocument(pdfReader);) {
            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);

            List<String> signedNames = signatureUtil.getSignatureNames();
            //遍历签名信息
            for (String signedName: signedNames) {

                PdfSignDTO pdfSignInfo = new PdfSignDTO();
                pdfSignInfo.setSignatureName(signedName);
                pdfSignInfo.setRevisionNumber(signatureUtil.getRevision(signedName));

                PdfPKCS7 pdfPKCS7 = signatureUtil.readSignatureData(signedName);
                pdfSignInfo.setSignDate(pdfPKCS7.getSignDate().getTime());
                pdfSignInfo.setDigestAlgorithm(pdfPKCS7.getDigestAlgorithm());
                pdfSignInfo.setLocation(pdfPKCS7.getLocation());
                pdfSignInfo.setReason(pdfPKCS7.getReason());
                pdfSignInfo.setEncryptionAlgorithm(pdfPKCS7.getEncryptionAlgorithm());

                X509Certificate signCert = pdfPKCS7.getSigningCertificate();
                pdfSignInfo.setSignerName(CertificateInfo.getSubjectFields(signCert).getField("CN"));

                PdfDictionary sigDict = signatureUtil.getSignatureDictionary(signedName);
                PdfString contactInfo = sigDict.getAsString(PdfName.ContactInfo);
                if (contactInfo != null) {
                    pdfSignInfo.setContactInfo(contactInfo.toString());
                }

                signInfoList.add(pdfSignInfo);

            }
        } catch(IOException e) {
            e.printStackTrace();
        }

        return signInfoList;

    }

}

签名信息封装实体类

package cn.cxyfyf.base.test.pdf.itext.config;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 获取签名信息实体类
 */
@Data
public class PdfSignDTO implements Serializable {

    private static final long serialVersionUID = 2098635697074981376L;

    /** 签名时间 */
    private Date signDate;
    /** 摘要算法 */
    private String digestAlgorithm;
    /** 原因 */
    private String reason;
    /** 地点 */
    private String location;
    /** 签名 */
    private String signatureName;
    /** 加密算法 */
    private String encryptionAlgorithm;
    /** 签名者 */
    private String signerName;
    /** 联系方式 */
    private String contactInfo;
    /** 修订号 */
    private int revisionNumber;
    /** 签名图片地址 */
    private String signImgPath;

}

二、相关测试类

public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        // pdf文件绝对路径
        String pdfFile = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshi.pdf";
        // 签名后Pdf文件要储存的绝对路径
        String signPdf = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshisign2.pdf";
        try(
                InputStream signInputStream = new FileInputStream(pdfFile);
                OutputStream singOutputStream = new FileOutputStream(signPdf);
            ) {
            // 电子签名
            SignUtils.signPdf(signInputStream, singOutputStream);
            // 获取签名信息
            List<PdfSignDTO> pdfSignInfo = SignUtils.getPdfSignInfo(new FileInputStream(pdfFile));
            for (PdfSignDTO pdfSignDTO : pdfSignInfo) {
                System.out.println(pdfSignDTO.toString());
            }
//            SignUtils.verifyPdf(signInputStream);
        }

        log.info("转换结束,耗时:{}ms",System.currentTimeMillis()-startTime);
}

 

本文作者:玩单机的零度

本文链接:https://www.cnblogs.com/cxyfyf/p/17089037.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   玩单机的零度  阅读(1915)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起