示例文件
您分享了一个示例文件在对您的问题的评论中由您的代码签名。
对该文件的分析(使用分析签名 test testPriyankaSignatureSampleinformedconsent_Signed
)表明实际签名的哈希是
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
这应该是经过身份验证的属性的哈希值,但它们的哈希值是
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
带符号的字节范围哈希是
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
所以它也不是这个哈希值。因此,尚不清楚该签名哈希值来自何处。
最终事实证明
Web 服务上的编码实用程序不同,因此解码的哈希值是错误的,并且MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)
不需要。这一行给出了错误的哈希值。
签署先前签署的文件
随后又出现了一个新问题:
当我签署已签名的文件时,该文件正在获得签名,但签名无效。它显示“签名字节范围无效”。
要签署已签名的 pdf,您必须使用附加模式。为此你需要一个不同的PdfStamper.createSignature
重载多一个参数,aboolean
,您设置为true
.
原因是通常(即没有激活附加模式) iText 重新组织内部 PDF 结构并删除未使用的对象。在已签名的 PDF 中,这通常会移动(已存在的)签名的位置,从而使签名结构无效。使用附加模式不过,iText 保持原始 PDF 字节不变,仅附加新数据。
更改哈希值
我只是很困惑为什么同一文件的字节“sh”的值每次都会发生变化?实际上,我想存储文件的哈希值。是因为“cal”吗?
事实上,每次您开始操作 PDF 时,结果都会获得一个新的唯一 ID。此外,还存储修改时间。对于签名用例,签名时间也有所不同。
我可以一次获取哈希值并将其存储在数据库中并稍后对哈希值进行签名并附加到pdf中吗?
你要么必须
- 暂时保持印章和外观打开,否则您必须
- 保留准备好的文件,否则你必须
- 修补 iText 以允许您设置签名时间、操作时间和 id 的固定值。
第一个选择对我来说是不可能的。我没有得到第二个第三个选项。您能详细说明两者或提供我可以参考的任何参考资料吗?
修补 iText 以强制每个文件使用固定哈希值
好吧,首先是第三个选项,即修补 iText,通常是您不想做的事情,因为它会使合并以后的 iText 更新变得困难。
OpenPdf(一个较旧的 iText 分支)包含一个补丁,添加EnforcedModificationDate
, OverrideFileId
, and IncludeFileID
属性到PdfStamper
. (PdfSignatureAppearance
已经有一个SignDate
属性。)应用此补丁是为了允许 eSignature DSS 在其签名过程中使用 OpenPdf(还包括创建签名的 PDF 两次,因此需要固定的哈希值)。
您可能不想切换到这个旧的 iText 分支,因为它错过了新 iText 版本的许多修复和新选项。
保留已经准备好的文件
因此,您可能应该保留最初创建的文件带有空签名,例如在某个临时文件夹中,然后应用延期签约一旦您检索到最终签名。
这本质上就是 iText 示例的内容C4_09_延迟签名就是这样,它首先创建一个包含所有内容的中间 PDF,仅缺少签名字节:
public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
}
仅在第二步中注入实际签名:
public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
MakeSignature.signDeferred(reader, fieldname, os, external);
}
using
class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected PrivateKey pk;
protected Certificate[] chain;
public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
this.pk = pk;
this.chain = chain;
}
public byte[] sign(InputStream is) throws GeneralSecurityException {
try {
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
String hashAlgorithm = signature.getHashAlgorithm();
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
这里的has是在第二步中计算的,但是你不需要这样做,你可以
- 已经在第一步中计算出来,不使用原始数据
ExternalBlankSignatureContainer
as in emptySignature()
上面但是将其扩展为计算哈希值MyExternalSignatureContainer
does,
- 将此值存储在您的数据库中,并且
- (一旦您检索到签名)使用以下变体注入该签名
MyExternalSignatureContainer
它不计算哈希值,而是准确注入返回的签名。