使用itextpdf对pdf进行签名盖章

package com.zhou.stamp;

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 lombok.Data;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Calendar;

/**
 * @author lang.zhou
 * @since 2023/8/1 13:42
 */
@Data
public class PDFStamper {

    private String pdf;


    public static void main(String[] args) throws Exception{
        PDFStamper stamper = new PDFStamper();
        stamper.setPdf("C:\\Users\\zhou\\Desktop\\2.pdf");
        StampOption option = new StampOption();
        PdfSignDto signDto = new PdfSignDto();
        signDto.setContact("zhoulang");
        signDto.setLocation("cn");
        signDto.setSignDate(Calendar.getInstance());
        signDto.setReason("盖章");
        option.setSignDto(signDto);
        //盖章图片
        option.setImgPath("C:\\Users\\zhou\\Desktop\\stamp2.jpeg");
        //盖章坐标(中心点)
        option.setX(200);
        option.setY(200);
        //章宽高,为空默认图片实际尺寸
        option.setWidth(100);
        option.setHeight(100);
        option.setPage(1);

        stamper.stamp(new FileInputStream("C:\\Users\\zhou\\Desktop\\1.pfx"),"证书密码",option, new FileOutputStream("C:\\Users\\zhou\\Desktop\\3.pdf"));
    }

    /**
     * 计算签章的对角线坐标(左上,右下)
     */
    private float[] calcImagePosition(Image image, StampOption stampOption){
        float[] pos = new float[4];
        float w = stampOption.getWidth();
        float h = stampOption.getHeight();
        if(w == 0 || h == 0){
            w = image.getWidth();
            h = image.getHeight();
        }
        pos[0] = stampOption.getX() - w / 2;
        pos[1] = stampOption.getY() - h / 2;
        pos[2] = stampOption.getX() + w / 2;
        pos[3] = stampOption.getY() + h / 2;
        return pos;
    }

    /**
     * 签名盖章
     * @param p12           pkcs12/pfx格式证书文件流
     * @param password      证书密码
     * @param stampOption   盖章选项
     * @param out           盖章后输出文件流
     */
    @SneakyThrows
    public void stamp(InputStream p12, String password, StampOption stampOption, OutputStream out){
        //读取keystore ,获得私钥和证书链
        KeyStore ks = KeyStore.getInstance("PKCS12");
        char[] chars = password.toCharArray();
        ks.load(p12, chars);
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, chars);
        Certificate[] chain = ks.getCertificateChain(alias);

        PdfReader pdfReader = new PdfReader(pdf);
        //目标文件输出流
        //创建签章工具PdfStamper ,最后一个boolean参数
        //false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
        //true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
        PdfStamper stamper = PdfStamper.createSignature(pdfReader, out, '\0', null, false);

        //读取图章图片,这个image是itext包的image
        Image image = Image.getInstance(stampOption.getImgPath());

        PdfSignDto signDto = stampOption.getSignDto();

        // 获取数字签章属性对象,设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        BeanUtils.copyProperties(signDto,appearance);

        //计算对角线坐标(左上,右下)
        float[] pos = calcImagePosition(image, stampOption);

        //设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
        //签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
        //四个参数的分别是,图章左上角x,图章左上角y,图章右下角x,图章右下角y
        appearance.setVisibleSignature(new Rectangle(pos[0], pos[1], pos[2], pos[3]), stampOption.getPage(), "sig1");

        appearance.setSignatureGraphic(image);
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
        //设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

        // 这里的itext提供了2个用于签名的接口,可以自己实现
        // 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();
        // 签名算法
        ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);
        // 调用itext签名方法完成pdf签章CryptoStandard.CMS 签名方式,建议采用这种
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);

        stamper.close();
        pdfReader.close();
    }

}
import lombok.Data;

/**
 * @author lang.zhou
 * @since 2023/8/1 13:47
 */
@Data
public class StampOption {

    private float x = 0f;

    private float y = 0f;

    private float width = 0f;

    private float height = 0f;

    /**
     * 盖章页数
     */
    private int page = 1;

    /** 签名图片地址 */
    private String imgPath;

    private PdfSignDto signDto;

}

import lombok.Data;
import java.util.Calendar;

/**
 * 签名信息实体类
 * @author lang.zhou
 * @since 2023/8/1 13:41
 */
@Data
public class PdfSignDto {
    private static final long serialVersionUID = 1L;

    /** 签名时间 */
    private Calendar signDate;

    /** 摘要算法 */
    private String digestAlgorithm;

    /** 原因 */
    private String reason;

    /** 地点 */
    private String location;

    /** 签名 */
    private String signatureName;

    /** 加密算法 */
    private String encryptionAlgorithm;

    /** 签名者 */
    private String signerName;

    /** 联系方式 */
    private String contact;

    /** 修订号 */
    private int revisionNumber;

}

你可能感兴趣的:(pdf,java)