Use itextpdf to sign and stamp 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("Stamp");
        option.setSignDto(signDto);
        //stamp image
        option.setImgPath("C:\Users\zhou\Desktop\stamp2.jpeg");
        //stamp coordinates (center point)
        option.setX(200);
        option.setY(200);
        //Chapter width and height, empty default image actual size
        option. setWidth(100);
        option. setHeight(100);
        option.setPage(1);

        stamper.stamp(new FileInputStream("C:\Users\zhou\Desktop\1.pfx"),"certificate password",option, new FileOutputStream(\ "C:\Users\zhou\Desktop\3.pdf"));
    }

    /**
     * Calculate the diagonal coordinates of the signature (upper left, lower right)
     */
    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;
    }

    /**
     * Signed and sealed
     * @param p12 pkcs12/pfx format certificate file stream
     * @param password certificate password
     * @param stampOption stamp option
     * @param out Output file stream after stamping
     */
    @SneakyThrows
    public void stamp(InputStream p12, String password, StampOption stampOption, OutputStream out){
        //Read keystore, get private key and certificate chain
        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);
        // target file output stream
        //Create the signature tool PdfStamper, the last boolean parameter
        //If false, the pdf file is only allowed to be signed once, multiple signatures, the last one is valid
        //If true, the pdf can be appended with a signature, and the signature verification tool can identify whether the document has been modified after each signature
        PdfStamper stamper = PdfStamper.createSignature(pdfReader, out, '\0', null, false);

        //Read the stamp image, this image is the image of the itext package
        Image image = Image.getInstance(stampOption.getImgPath());

        PdfSignDto signDto = stampOption. getSignDto();

        // Get the digital signature attribute object, set the attributes of the digital signature
        PdfSignatureAppearance appearance = stamper. getSignatureAppearance();
        BeanUtils.copyProperties(signDto,appearance);

        // Calculate the diagonal coordinates (upper left, lower right)
        float[] pos = calcImagePosition(image, stampOption);

        //Set the signature position, page number, signature domain name, when multiple signatures are added, the signature pre-name cannot be the same
        //The position of the signature is the position coordinates of the stamp relative to the pdf page, and the origin is the lower left corner of the pdf page
        //The four parameters are x in the upper left corner of the stamp, y in the upper left corner of the stamp, x in the lower right corner of the stamp, and y in the lower right corner of the stamp
        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);
        //Set the display mode of the stamp, the following selection is to display only the stamp (there are other modes, which can display the stamp and signature description together)
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

        // Here itext provides 2 interfaces for signing, which can be implemented by yourself
        // digest algorithm
        ExternalDigest digest = new BouncyCastleDigest();
        // signature algorithm
        ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms. SHA256, null);
        // Call the itext signature method to complete the pdf signature CryptoStandard.CMS signature method, it is recommended to use this
        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;

    /**
     * Number of stamped pages
     */
    private int page = 1;

    /** Signature image address */
    private String imgPath;

    private PdfSignDto signDto;

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

/**
 * Signature information entity class
 * @author lang.zhou
 * @since 2023/8/1 13:41
 */
@Data
public class PdfSignDto {
    private static final long serialVersionUID = 1L;

    /** Signature time */
    private Calendar signDate;

    /** Digest Algorithm */
    private String digestAlgorithm;

    /** reason */
    private String reason;

    /** Place */
    private String location;

    /** sign */
    private String signatureName;

    /** Encryption Algorithm */
    private String encryptionAlgorithm;

    /** Signer */
    private String signerName;

    /** Contact information */
    private String contact;

    /** Amendment No */
    private int revisionNumber;

}