在 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 时丢失了。
根据上一个问题给出的答案: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 时丢失了。