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; }