PDF file structure signature

Foreword

Recently, I have seen that more and more contracts on the Internet are sent in the form of PDF, allowing users to feel the real legal effect. PDF contract documents will have signatures.

It has two benefits. One is that it allows users to see a formal company signature when seeing the contract; the other is that the signature of the PDF file can prevent tampering and has certain legal effects.

What is a digital signature?

Digital signatures are based on hash algorithms and public key encryption algorithms. For plain text messages, a hash algorithm is first used to calculate the digest, and then the private key is used to encrypt the digest. The resulting value is the digital signature of the original text.

Digital signature (also known as public key digital signature, electronic signature) is a kind of ordinary physical signature similar to written on paper, but it is implemented using technology in the field of public key encryption and is used to identify digital information.

A set of digital signatures usually defines two complementary operations, one for signing and another for verification.

The principle of digital signature

The sender generates or obtains a unique encryption cipher suite, including private and public keys.
Sender writes email
The sender obtains a summary of the email message using a secure digest algorithm
The sender then uses the private key to encrypt the message digest to obtain a digital signature.?
The sender attaches a digital signature to the message.
The sender sends a digital signature and information (encrypted or unencrypted) to the electronic recipient.
The recipient uses the sender’s public password (public key) to confirm the sender’s electronic signature, that is, the sender’s digital signature is decrypted through the public key to obtain the information summary.
The recipient obtains a “message digest” of the message (encrypted or unencrypted) using the same secure digest algorithm.
The recipient compares the two message digests. If they are the same, the recipient can be confident that the message has not changed in any way since it was issued.
The recipient obtains a certification certificate from the certification authority (or obtains it from the sender of the message). This certificate is used to confirm the authenticity of the digital signature on the message sent by the sender. The certification authority is a typical example in the digital signature system. A third party entrusted with managing the certification business. The certificate contains the sender’s public password and name (and possibly other additional information), digitally signed by the certification authority.

PDF signature file structure
PDF signature: put the picture into PDF, PDF + picture together as the original text for signature

PDF signature format:
adbe.pkcs7.detached (P7 without content/original text)
adbe.pkcs7.sha1 (P7 contains content. First do SHA1 on the PDF data, and then use the SHA1 data as P7 content, which is equivalent to making 2 digests)
adbe.x509.rsa_sha1 (digital certificate + P1 signature): In actual scenarios, it is commonly known as naked signature. The hash algorithm is not necessarily SHA1, but may be SHA256. When I encountered a need to verify Didi invoices, I used SHA256 for hashing and modified the itext source code to support signature verification.
ETSI.CAdES.detached (CAdES without content)

One signature
/ByteRange [offset1,len1,offset2,len2], indicates the position of the two original parts of the PDF.
Example: [0,840,960,240], 0 and 960 are offsets, 840 and 240 are lengths.
0-840: It is the first part of the original signature text. This part will contain a picture, which together with the original data becomes the original signature text;
960-1200: It is the second part of the original signature;
840-960: It needs to be reserved for signature data. The specific amount reserved can be infinite. The disadvantage is that the final file will be very large. You can also sign the first part first and the approximate length will be obtained. Here Based on this, add some length as a reserve.


Multiple signatures


PDF signature code example
You need to use the jar packages of itextpdf and BouncyCastleProvider:
itextpdf-5.5.5-with-asian.jar
bcpkix-jdk15on-1.60.jar
bcprov-jdk15on-1.60.jar

package cn.com.gs.common.util.pdf;

import cn.com.gs.common.define.Constants;
import cn.com.gs.common.exception.NetGSRuntimeException;
import cn.com.gs.common.util.StringUtil;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.ByteArrayOutputStream;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.ArrayList;

/**
 * Reference article: https://blog.csdn.net/tomatocc/article/details/80762507
 * @author Administrator
 */
public class PdfStampUtil {

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * Add pictures and signature to PDF
     * @param pdfData pdf data
     * @param photoData image data
     * @param pageNumber page number
     * @param x x coordinate
     * @param y y coordinate
     * @param chain certificate chain
     * @param privateKey private key
     * @param hashAlg digest algorithm
     * @return
     * @throwsException
     */
    public byte[] sign(byte[] pdfData, byte[] photoData, int pageNumber, float x, float y,
                       Certificate[] chain, PrivateKey privateKey, String hashAlg) throws Exception {
        PdfReader reader = new PdfReader(pdfData);

        /*
         * Create signature tool PdfStamper,
         * The second parameter is the output stream. The signed file is placed in this output stream. We get
         * Whether the last boolean parameter is allowed to be appended with a signature
         * If false, the PDF file is only allowed to be signed once. If signed multiple times, the last one is valid.
         * If true, the PDF can be additionally signed, and the signature verification tool can identify whether the document has been modified after each signature.
         */
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfStamper stamper = PdfStamper.createSignature(reader, outputStream, '\0', null, true);

        // 1.Set PdfSignatureAppearance
        PdfSignatureAppearance sap = stamper.getSignatureAppearance();
        // 1.1 Set stamp image
        Image image = Image.getInstance(photoData);
        sap.setSignatureGraphic(image);
        // 1.2 Set the display mode of the stamp. Here GRAPHIC only displays the stamp (there are other modes where the stamp and signature description can be displayed together). If not set, the default is to display the description.
        sap.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
        // 1.3 Set the stamp position, page number, and signature field name. When appending signatures multiple times, the signature prename cannot be the same. The image size is affected by the size of the form field (too small will cause compression)
        // The coordinates of the signature are the position coordinates of the stamp relative to the PDF page. The origin is the lower left corner of the PDF page.
        //The four parameters are: x in the lower left corner of the stamp, y in the lower left corner of the stamp, x in the upper right corner of the stamp, y in the upper right corner of the stamp
        float imageWidth = image.getWidth() * 72f / Constants.DPI;
        float imageHeight = image.getHeight() * 72f / Constants.DPI;
        float ux = x + imageWidth;
        float uy = y + imageHeight;
        sap.setVisibleSignature(new Rectangle(x, y, ux, uy), pageNumber, StringUtil.genDigitRandom(10));

        // 2. Summary algorithm
        ExternalDigest digest = new BouncyCastleDigest();
        // 3. Signature algorithm
        ExternalSignature signature = new PrivateKeySignature(privateKey, hashAlg, null);
        // Signature
        MakeSignature.signDetached(sap, digest, signature, chain,
                null, null, null, 0, MakeSignature.CryptoStandard.CADES);

        stamper.close();
        reader.close();
        return outputStream.toByteArray();

    }

    /**
     * PDF verification signature
     * @param pdfData
     * @return
     * @throwsException
     */
    public boolean verifySign(byte[] pdfData) throws Exception {
        PdfReader reader = new PdfReader(pdfData);
        AcroFields fields = reader.getAcroFields();
        ArrayList<String> names = fields.getSignatureNames();
        for (int i = 0, size = names.size(); i < size; i + + ) {
            String signName = (String) names.get(i);
            PdfDictionary dictionary = fields.getSignatureDictionary(signName);

            PdfName sub = dictionary.getAsName(PdfName.SUBFILTER);
            if (PdfName.ETSI_CADES_DETACHED.equals(sub)) {
                PdfPKCS7 pkcs7 = fields.verifySignature(signName);
                return pkcs7.verify();

            } else {
                throw new NetGSRuntimeException("SubFilter type not supported yet:" + sub);
            }
        }
        return false;
    }

}


PDF Example
Parse a signature file

RSA signature
The real content under /Contents is a DER-encoded PKCS#7 data object


SM2 signature
The real content under /Contents is data that conforms to the signature specification, such as the signature under the 38540 specification:


Code Example

public void getSealFromPDFStamp() throws Exception {
        String pdfStampPath = "f:/temp/stamp.pdf";
        String stampPath = "f:/temp/1.stamp";

        byte[] pdfData = FileUtil.getFile(pdfStampPath);

        PdfReader reader = null;
        try {
            reader = new PdfReader(pdfData);
            AcroFields af = reader.getAcroFields();

            ArrayList<String> names = af.getSignatureNames();
            // Get the signature value of each signature field
            for (String name : names) {
                PdfDictionary dictionary = af.getSignatureDictionary(name);
                byte[] bytes = dictionary.getAsString(PdfName.CONTENTS).getBytes();
                String hexContents = HexUtil.byte2Hex(bytes);
                //Remove the trailing 0 padding
                while (hexContents.endsWith("00"))
                    hexContents = hexContents.substring(0, hexContents.length() - 2);

                FileUtil.storeFile(stampPath, HexUtil.hex2Byte(hexContents));
            }


        } catch (Exception e) {
            throw e;
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (Exception e) {
            }
        }
    }