在Itext 7中,如何使用2个步骤签署pdf?

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

这是已经包含名为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类:

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

}

回答(1)

2 years ago

为什么这一步的结果无效

因为你基本上发现了一个bug ...;)

您的示例输入文件有一个触发错误的功能:使用对象流进行压缩 .

当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);

有了这个补丁,你的代码返回的PDF就不再像上面那样被破坏了 .


顺便说一下,iText 5确实在 if 块上方的相应 PdfSignatureAppearance.preClose(HashMap<PdfName, Integer>) 块中包含一条类似的行,对应于上面的 if 块 . 在重构到iText 7期间似乎已经丢失了 .