itext5实现pdf文件的电子签名、签章

一、itextPDF的相关依赖

<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itextpdf</artifactId>
  <version>5.5.10</version>
</dependency>

<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itext-asian</artifactId>
  <version>5.2.0</version>
</dependency>

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk16</artifactId>
  <version>1.46</version>
</dependency>

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcpkix-jdk15on</artifactId>
  <version>1.62</version>
</dependency>
<!-- 条形码、二维码生成 -->
<dependency>
  <groupId>com.google.zxing</groupId>
  <artifactId>core</artifactId>
  <version>3.3.0</version>
</dependency>

<dependency>
  <groupId>com.google.zxing</groupId>
  <artifactId>javase</artifactId>
  <version>3.3.0</version>
</dependency>

二、java工具keytool生成数字证书文件

 Keytool是用于管理密钥和证书的工具,位于%JAVA_HOME%/bin目录 
使用JDK的keytool工具:

1.在d盘创建keystore文件夹
2.进入jdk安装路径 然后到bin目录下  然后在目录上方输入cmd
3.在命令窗口执行

keytool -genkeypair -alias serverkey -keypass 111111 -storepass 111111 -dname "CN=Xu,OU=S,O=P,L=ShangHai, ST=ShangHai,C=CN" -keyalg RSA -keysize 2048 -validity 3650 -keystore D:\tmp\server.keystore

介绍如下:

storepass keystore 文件存储密码,不加这个参数会在后面要求你输入密码keypass 私钥加解密密码
alias 实体别名(包括证书私钥)
dname 证书个人信息 
keyalg 采用公钥算法,默认是DSA,这里采用RSA 
keysize 密钥长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA) 
validity 有效期 
keystore 指定keystore文件储存位置

4.待上面命令执行完后再执行

 keytool -importkeystore -srckeystore D:\keystore\server.keystore -destkeystore D:\keystore\whj.p12 -srcalias whj -destalias serverkey -srcstoretype jks -deststoretype pkcs12 -srcstorepass 111111 -deststorepass 111111 -noprompt

三、代码

  • 工具类代码

package com.cros.pdf;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.*;
import com.cros.entity.SignatureInfo;

import java.io.*;
import java.security.GeneralSecurityException;

public class ItextUtil {

    public static final char[] PASSWORD = "111111".toCharArray();// keystory密码

    @SuppressWarnings("resource")
    public void sign(String src, String target, SignatureInfo signatureInfo) {
        InputStream inputStream = null;
        FileOutputStream outputStream = null;
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        try {
            inputStream = new FileInputStream(src);
            ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream();
            PdfReader reader = new PdfReader(inputStream);
            // 创建签章工具PdfStamper ,最后一个boolean参数是否允许被追加签名
            // false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
            // true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
            PdfStamper stamper = PdfStamper.createSignature(reader,
                    tempArrayOutputStream, '\0', null, true);
            // 获取数字签章属性对象
            PdfSignatureAppearance appearance = stamper
                    .getSignatureAppearance();
            appearance.setReason(signatureInfo.getReason());
            appearance.setLocation(signatureInfo.getLocation());
            // 设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样 图片大小受表单域大小影响(过小导致压缩)
            // 签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
            // 四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
            //四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
            appearance.setVisibleSignature(new Rectangle(280, 220, 140, 600), 1, "sig1");
            // 读取图章图片
            Image image = Image.getInstance(signatureInfo.getImagePath());
            appearance.setSignatureGraphic(image);
            appearance.setCertificationLevel(signatureInfo
                    .getCertificationLevel());
            // 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
            appearance.setRenderingMode(signatureInfo.getRenderingMode());
            // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
            // 摘要算法
            ExternalDigest digest = new BouncyCastleDigest();
            // 签名算法
            ExternalSignature signature = new PrivateKeySignature(
                    signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(),
                    null);
            // 调用itext签名方法完成pdf签章 //数字签名格式,CMS,CADE
            MakeSignature.signDetached(appearance, digest, signature,
                    signatureInfo.getChain(), null, null, null, 0,
                    MakeSignature.CryptoStandard.CADES);
            inputStream = new ByteArrayInputStream(
                    tempArrayOutputStream.toByteArray());
            // 定义输入流为生成的输出流内容,以完成多次签章的过程
            result = tempArrayOutputStream;
            outputStream = new FileOutputStream(new File(target));
            outputStream.write(result.toByteArray());
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != outputStream) {
                    outputStream.close();
                }
                if (null != inputStream) {
                    inputStream.close();
                }
                if (null != result) {
                    result.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 实体类代码

package com.cros.entity;

import com.itextpdf.text.pdf.PdfSignatureAppearance;
import java.security.PrivateKey;
import java.security.cert.Certificate;

public class SignatureInfo {
    private String reason; //签名的原因,显示在pdf签名属性中
    private String location;//签名的地点,显示在pdf签名属性中
    private String digestAlgorithm;//摘要算法名称,例如SHA-1
    private String imagePath;//图章路径
    private String fieldName;//表单域名称
    private Certificate[] chain;//证书链
    private PrivateKey pk;//签名私钥
    private int certificationLevel = 0; //批准签章
    private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式:仅描述,仅图片,图片和描述,签章者和描述
    private float rectllx;//图章左下角x
    private float rectlly;//图章左下角y
    private float recturx;//图章右上角x
    private float rectury;//图章右上角y

    public float getRectllx() {
        return rectllx;
    }

    public void setRectllx(float rectllx) {
        this.rectllx = rectllx;
    }

    public float getRectlly() {
        return rectlly;
    }

    public void setRectlly(float rectlly) {
        this.rectlly = rectlly;
    }

    public float getRecturx() {
        return recturx;
    }

    public void setRecturx(float recturx) {
        this.recturx = recturx;
    }

    public float getRectury() {
        return rectury;
    }

    public void setRectury(float rectury) {
        this.rectury = rectury;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getDigestAlgorithm() {
        return digestAlgorithm;
    }

    public void setDigestAlgorithm(String digestAlgorithm) {
        this.digestAlgorithm = digestAlgorithm;
    }

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public Certificate[] getChain() {
        return chain;
    }

    public void setChain(Certificate[] chain) {
        this.chain = chain;
    }

    public PrivateKey getPk() {
        return pk;
    }

    public void setPk(PrivateKey pk) {
        this.pk = pk;
    }

    public int getCertificationLevel() {
        return certificationLevel;
    }

    public void setCertificationLevel(int certificationLevel) {
        this.certificationLevel = certificationLevel;
    }

    public PdfSignatureAppearance.RenderingMode getRenderingMode() {
        return renderingMode;
    }

    public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) {
        this.renderingMode = renderingMode;
    }
}
  • 测试代码

package com.cros.pdf;

import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.cros.entity.SignatureInfo;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import static com.cros.pdf.ItextUtil.PASSWORD;

public class PdfStamp {
    public static void main(String[] args) {
        try {
            ItextUtil app = new ItextUtil();
            // 将证书文件放入指定路径,并读取keystore ,获得私钥和证书链
            String pkPath = "src/main/resources/cros.p12";
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(new FileInputStream(pkPath), PASSWORD);
            String alias = ks.aliases().nextElement();
            PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
            // 得到证书链
            Certificate[] chain = ks.getCertificateChain(alias);
            //需要进行签章的pdf
            String path = "D:\\project\\Itext\\src\\main\\resources\\pdf\\pdfdemo.pdf";
            // 封装签章信息
            SignatureInfo signInfo = new SignatureInfo();
            signInfo.setReason("理由");
            signInfo.setLocation("位置");
            signInfo.setPk(pk);
            signInfo.setChain(chain);
            signInfo.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
            signInfo.setDigestAlgorithm(DigestAlgorithms.SHA1);
            signInfo.setFieldName("demo");

            // 签章图片
            signInfo.setImagePath("D:\\project\\Itext\\src\\main\\resources\\pdf\\chapter.png");
            signInfo.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
             值越大,代表向x轴坐标平移 缩小 (反之,值越小,印章会放大)
            signInfo.setRectllx(100);
             值越大,代表向y轴坐标向上平移(大小不变)
            signInfo.setRectlly(200);
            // 值越大   代表向x轴坐标向右平移  (大小不变)
            signInfo.setRecturx(150);
            // 值越大,代表向y轴坐标向上平移(大小不变)
            signInfo.setRectury(150);

            //签章后的pdf路径
            app.sign(path, "D:\\project\\Itext\\src\\main\\resources\\pdf\\out.pdf", signInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Comment