使用java和iText对PDF哈希进行签名
我有一个生成PDF的应用程序,需要签名 我们没有签署文档的证书,因为它们位于HSM中,我们可以使用证书的唯一方法是使用Web服务使用java和iText对PDF哈希进行签名,java,pdf,hash,itext,digital-signature,Java,Pdf,Hash,Itext,Digital Signature,我有一个生成PDF的应用程序,需要签名 我们没有签署文档的证书,因为它们位于HSM中,我们可以使用证书的唯一方法是使用Web服务 PdfReader reader = new PdfReader(src); reader.setAppendable(true); ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileOutputStream fout = new FileOutputStream
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;
在这一点上,我们得到一个PDF签名,但签名无效。Adobe称“文档自签署以来已被修改或损坏”
我已经通过了,但是没有运气。示例文件
您在对问题的评论中共享了由代码签名的
对该文件的分析(使用testtestpriyankasignaturesampleinformedapprovement\u Signed
执行)表明,实际签名的哈希是
1134ED96F42AC7352E546BE0E906C0BF5D4429AEAFAC39145DB40A0BB7E817B
它应该是经过身份验证的属性的散列,但它们的散列是
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BBDFB67911C50CC5D949。
有符号字节范围散列为
D7917CF3BA9FE5D5B626ED825965D699F7C54DBF9F18DECE18EF8DD36DC4C28,
所以这也不是散列。因此,不清楚该签名散列来自何处
最终证明
web服务上的编码实用程序不同,原因是解码的哈希值错误,并且不需要MessageDigest(sh=MessageDigest.getInstance(“SHA256”,“BC”).digest(sh))
。这一行给出了错误的哈希值
签名以前签名的文件
此后,出现了一个新问题:
当我对已经签名的文件进行签名时,该文件正在获得签名,但签名无效。其显示“签名字节范围无效”
对于已签名的PDF,您必须使用附加模式。为此,您需要一个不同的PdfStamper.createSignature
重载,再加上一个参数boolean
,将其设置为true
原因是通常(即不激活附加模式)iText会重新组织内部PDF结构并删除未使用的对象。在已签名PDF中,这通常会移动(已存在)签名的位置,从而使签名结构无效。不过,使用追加模式,iText会保持原始PDF字节不变,只追加新数据
更改哈希
我只是不明白为什么字节“sh”的值每次都在同一个文件中改变?实际上,我想存储文件的哈希值。是因为“卡尔”吗
实际上,每次您开始操作PDF时,结果都会得到一个新的唯一ID。此外,修改时间也会被存储。对于签名用例,签名时间也不同
我可以一次获取散列并将其存储在db中,稍后对散列进行签名并附加到pdf中吗
你要么必须
- 保持压模和外观暂时打开,否则你必须
- 保留准备好的文件,否则你必须
- 修补iText以允许您设置签名时间、操作时间和id的固定值
EnforcedModificationDate
,OverrideFileId
,以及IncludeFileID
属性添加到PdfStamper
。(PdfSignatureAppearance
已具有SignDate
属性。)此修补程序已应用于允许eSignature DSS在其签名过程中使用OpenPdf(这还包括创建签名PDF两次,因此需要固定哈希值)
您可能不想切换到这个旧的iText fork,因为它错过了许多新版本的修复和新选项
保留已准备好的文件
因此,您可能应该将原始创建的文件保留为空签名,例如在某个临时文件夹中,并在检索最终签名后立即应用延迟签名
这本质上就是iText示例的全部内容,它首先创建一个包含所有内容的中间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);
}
使用
这里的has是在第二步计算的,但是您不需要这样做,您可以
- 在第一步中,已通过不使用原始的
来计算它,如上面的ExternalBlankSignatureContainer
中所述,而是扩展它来计算散列,如emptySignature()
所做MyExternalSignatureContainer
- 将此值存储在数据库中,以及
- (检索签名后)使用MyExternalSignatureContainer的变体注入该签名,该变体不计算哈希,而是精确注入返回的签名
testpriyankasignaturesampleinformedapprovement\u Signed
执行)表明,实际签名的哈希是
1134ED96F42AC7352E546BE0E906C0BF5D4429AEAFAC39145DB40A0BB7E817B
它应该是经过身份验证的属性的散列,但它们的散列是
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BBDFB67911C50CC5D949。
有符号字节范围散列为
D7917CF3BA9FE5D5B626ED825965D699F7C54DBF9F18DECE18EF8DD36DC4C28,
所以这也不是散列。因此,不清楚该签名散列来自何处
最终证明
web服务上的编码实用程序不同,因此解码的哈希值
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) {
}
}