在 Itext 7 中,如何通过 2 个步骤签署 pdf?

In Itext 7, how to sign a pdf with 2 steps?

根据上一个问题给出的答案:In Itext 7, how to get the range stream to sign a pdf?,我尝试重新实现在 Itext 5 中工作的两步签名方法,但在尝试重新打开第一个文档结果时遇到问题步骤(使用 PdfReader 或 pdf reader)。(无效文档)

这是文档的预签名部分,该文档已经包含名为 certification 的空签名字段...为什么此步骤的结果无效?

PdfReader reader = new PdfReader(fis);
Path signfile = Files.createTempFile("sign", ".pdf");
FileOutputStream os = new FileOutputStream(signfile.toFile());
PdfSigner signer = new PdfSigner(reader, os, false);
signer.setFieldName("certification"); // this field already exists
signer.setCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
PdfSignatureAppearance sap = signer.getSignatureAppearance();
sap.setReason("Certification of the document");
sap.setLocation("On server");
sap.setCertificate(maincertificate);
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest,false);
 //IExternalSignatureContainer like BlankContainer
PreSignatureContainer external = new    PreSignatureContainer(PdfName.Adobe_PPKLite,PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 8192);
byte[] hash=external.getHash();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null,PdfSigner.CryptoStandard.CMS);// sh will be sent for signature

这是 PreSignatureContainer class :

public class PreSignatureContainer implements IExternalSignatureContainer {

private PdfDictionary sigDic;
private byte hash[];


public PreSignatureContainer(PdfName filter, PdfName subFilter) {

    sigDic = new PdfDictionary();
    sigDic.put(PdfName.Filter, filter);
    sigDic.put(PdfName.SubFilter, subFilter);
}

@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
    String hashAlgorithm = "SHA256";
    BouncyCastleDigest digest = new BouncyCastleDigest();

    try {
    this.hash= DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
    } catch (IOException e) {
        throw new GeneralSecurityException("PreSignatureContainer signing exception",e);
    }

    return new byte[0];
}

@Override
public void modifySigningDictionary(PdfDictionary signDic) {
    signDic.putAll(sigDic);

}

public byte[] getHash() {
    return hash;
}

public void setHash(byte hash[]) {
    this.hash = hash;
}

}

why is the result of this step invalid

因为您基本上发现了一个错误...;)

您的示例输入文件有一项功能会触发错误:它是使用对象流压缩的。

当 iText 操作此类文件时,它还会尝试将尽可能多的对象放入对象流中。不幸的是,它对签名字典也是如此。这很不幸,因为在写入整个文件后,它会尝试将一些信息(之前不可用)输入到该字典中,这会损坏压缩对象流。


你能做什么...

你可以

  • 等待 iText 开发解决这个问题——我想这不会花太长时间,但你可能没有时间等待;或
  • 将文件转换为不使用对象流的形式 - 这可以使用 iText 本身来完成,但你可能无法接受这意味着文件增长,或者可能文件已经签名,禁止任何此类转换;或
  • 修补 iText 7 以强制不将签名字典添加到对象流 - 这是一个微不足道的修补程序,但您可能不想使用修补过的库。

上面提到的补丁确实是微不足道的,方法PdfSigner.preClose(Map<PdfName, Integer>)包含这段代码:

if (certificationLevel > 0) {
    // add DocMDP entry to root
    PdfDictionary docmdp = new PdfDictionary();
    docmdp.put(PdfName.DocMDP, cryptoDictionary.getPdfObject());
    document.getCatalog().put(PdfName.Perms, docmdp); // TODO: setModified?
}

document.close();

cryptoDictionary.getPdfObject())就是我上面提到的签名词典。在 document.close() 期间,它被添加到对象流中,除非它之前已被写入输出。因此,您只需在 close 调用之前添加一个刷新该对象的调用,并通过参数明确表示不应将其添加到对象流中:

cryptoDictionary.getPdfObject().flush(false);

有了这个补丁,您的代码 returns 的 PDF 就不会再像上面那样损坏了。


顺便说一句,iText 5 确实在 if 块正上方对应的 PdfSignatureAppearance.preClose(HashMap<PdfName, Integer>) 中包含类似的行,对应于上面的 if 块。它似乎在重构到 iText 7 时丢失了。