1

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

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

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

}

4

1 回答 1

1

为什么这一步的结果无效

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

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

当 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 确实在与上面的块相对应的块上方的相应PdfSignatureAppearance.preClose(HashMap<PdfName, Integer>)右侧包含类似的行。它似乎在重构到 iText 7 期间丢失了。ifif

于 2016-08-26T10:18:31.477 回答