动态创建 pdf 并使用 itext pdf 对其进行签名

create pdf dynamically and sign it using itext pdf

我正在动态创建一个 PDF 文档,添加一个签名字段,然后尝试对其进行签名。签名工作正常,但出现异常:

"certfied by %, invalid signature and signature contains invalid data"

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Calendar;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
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.PdfWriter;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;

//Actual class 
public class SignPdf {

    // Signs the pdf
    public void signPdf(String src, String dest, boolean certified, boolean graphic) throws GeneralSecurityException, IOException, DocumentException{
        String path = "src/certs.pfx";
        String keystore_password = "pwd";
        String key_password = "pwd";
        KeyStore ks = KeyStore.getInstance("PKCS12", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        InputStream is = this.getClass().getResourceAsStream("/certs.pfx");
        ks.load(is, keystore_password.toCharArray());

        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        byte[] pdfByteArray = null;
        // creating dynamic document using itext.
        Document document = new Document();
        OutputStream baosPDF = new ByteArrayOutputStream();
        PdfWriter.getInstance(document, baosPDF);
        document.open();
        document.add(new Paragraph("Hello World!"));
        document.close();
        byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
        PdfReader reader = new PdfReader(bytearrayb);
        PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '[=10=]', null, true);

        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason("Security");
        appearance.setLocation("Footer");
        appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "DVA");
        if (certified) appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
        if (graphic) {
            appearance.setSignatureGraphic(Image.getInstance(RESOURCE));
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
        }
        appearance.setSignDate(Calendar.getInstance());
        appearance.setSignatureCreator("test");
        ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
        ExternalDigest digest = new BouncyCastleDigest();
        MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
        baosPDF.flush();
        baosPDF.close();

        BufferedOutputStream fs = new BufferedOutputStream(new FileOutputStream(new File("myFile121.pdf")));
        fs.write(((ByteArrayOutputStream) baosPDF).toByteArray());
        fs.flush();
        fs.close();
    }

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        SignPdf signatures = new SignPdf();
        try {
            signatures.signPdf(ORIGINAL, "", true, false);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

您重新使用 ByteArrayOutputStream 而未清除它:

OutputStream baosPDF = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baosPDF);
document.open();
document.add(new Paragraph("Hello World!"));
document.close();
byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
PdfReader reader = new PdfReader(bytearrayb);
PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '[=10=]', null, true);

根据 ByteArrayOutputStream 来源:

/**
 * Resets the <code>count</code> field of this byte array output 
 * stream to zero, so that all currently accumulated output in the 
 * output stream is discarded. The output stream can be used again, 
 * reusing the already allocated buffer space. 
 *
 * @see     java.io.ByteArrayInputStream#count
 */
public synchronized void reset()

因此,重新使用前重置:

byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
baosPDF.reset();
PdfReader reader = new PdfReader(bytearrayb);
PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '[=12=]', null, true);