我有一个生成PDF的应用程序,需要签名。
我们没有签署文档的证书,因为它们在HSM中,我们使用证书的唯一方法是使用Web服务。
PdfReader reader = new PdfReader(src);
reader.setAppendable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("Test");
appearance.setLocation("footer");
appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
appearance.setCertificate(certChain[0]);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
appearance.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest()
{
public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
{
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null, certChain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
byte[] hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
String hashPdf = new String(Base64.encodeBytes(sh));
String hashSignat = hashPdf;
这是我们的代码,首先,我们获得签名外观,并计算哈希值
在这一点上,我们有文档的哈希码。然后我们将哈希发送到webservice,我们得到签名的哈希码。
最后,我们将签名的哈希值放到PDF中:
sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null,
null, CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSign, 0, paddedSig, 0,
encodedSign.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
在这一点上,我们获得了PDF签名,但签名无效。 Adobe称“文件自签署后已被更改或损坏”。
我经历过Sign PDF using an external service and iText,PDF signature itext pkcs7 multi sign和Is it possible to sign a PDF document with hash and signed hash?,但没有运气。
您在评论中共享了由您的代码签名的example file。
对该文件的分析(使用AnalyzeSignatures测试testPriyankaSignatureSampleinformedconsent_Signed
执行)显示实际签名的哈希是
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
这应该是经过身份验证的属性的哈希值,但它们的哈希值是
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
带符号的字节范围哈希是
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
所以它不是这个哈希。因此,不清楚签名哈希来自何处。
最终证明了这一点
Web服务上的编码实用程序是不同的,因为解码的哈希值是错误的,并且不需要
MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)
。这条线给出了错误的哈希值。
此后出现了一个新问题:
当我签名时,已经签名的文件正在签名,但签名无效。它显示“签名字节范围无效”。
要签署已签名的pdf,您必须使用追加模式。为此你需要一个不同的PdfStamper.createSignature
重载与另一个参数,一个boolean
,你设置为true
。
原因是通常(即不激活附加模式)iText重新组织内部PDF结构并丢弃未使用的对象。在已签名的PDF中,这通常会移动(已存在的)签名的位置,该签名使签名结构无效。但是,使用追加模式,iText保持原始PDF字节不变,只附加新数据。
我只是混淆了为什么字节“sh”的值每次都在同一个文件的变化?实际上,我想存储文件的哈希值。是因为“cal”?
实际上,每次开始操作PDF时,结果都会获得一个新的唯一ID。此外,存储修改时间。在签名用例的情况下,签名时间也不同。
我可以一次获取哈希并将其存储在db中并稍后对哈希进行签名并附加到pdf中吗?
你要么必须
第一种选择对我来说是不可能的。我没有获得第二个第三选项。你能详细说明或提供我可以参考的任何参考资料吗?
好的,首先关闭第三个选项,修补iText,通常是你不想做的事情,因为它使后续的iText更新变得困难。
OpenPdf(一个较旧的iText分支)包含一个补丁,将EnforcedModificationDate
,OverrideFileId
和IncludeFileID
属性添加到PdfStamper
。 (PdfSignatureAppearance
已经拥有SignDate
属性。)此补丁已应用于允许eSignature DSS在其签名过程中使用OpenPdf(其中还包括创建签名的PDF两次,因此需要固定的散列值)。
您可能不想切换到这个旧的iText分支,因为它错过了许多修复和新的iText版本的新选项。
因此,您可能应该使用空签名保留最初创建的文件,例如在某个临时文件夹中,一旦检索到最终签名,就应用延迟签名。
这基本上是iText示例C4_09_DeferredSigning的全部内容,它首先创建一个包含所有内容的中间PDF,只丢失signatrue字节:
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);
}
运用
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
中的原始emptySignature()
,而是扩展它以计算MyExternalSignatureContainer
所做的哈希值,MyExternalSignatureContainer
的变体注入该签名,该变量不计算哈希值,而是精确地注入返回的签名。