我有一个生成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;
这是我们的代码,首先,我们获得签名外观,并计算哈希值
至此,我们有了文档的哈希码。然后,我们将哈希发送到Web服务,并获得签名的哈希代码。
最后,我们将签名的哈希值放入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?,但是没有运气。
答案 0 :(得分:0)
您在问题的注释中共享了一个由代码签名的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,仅缺少签名字节:
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) {
}
}
此处是在第二步中计算的,但您不必这样做,可以
ExternalBlankSignatureContainer
那样使用原始的emptySignature()
来进行计算,而是像MyExternalSignatureContainer
那样将其扩展为计算哈希,MyExternalSignatureContainer
的变体注入该签名,该变体不计算哈希值,而是精确注入返回的签名。