使用外部服务和 iText 签署 PDF [英] Sign PDF using an external service and iText

查看:23
本文介绍了使用外部服务和 iText 签署 PDF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个场景.

我有一个生成 PDF 的应用程序,需要签名.

I have an application that generates a PDF, and that needs to be signed.

我们没有用于签署文档的证书,因为它们在 HSM 中,我们可以使用证书的唯一方法是使用网络服务.

We have not the certificates to sign the document, because they're in a HSM, and the only way we could make use of the certificates is using a webservice.

此网络服务提供两个选项,发送 PDF 文档并返回已签名的 pdf,或发送将被签名的哈希值.

This webservice, offers two options, send the PDF document, and it returns a signed pdf, or send a hash that will be signed.

第一个选项不可行,因为 PDF 是在没有时间戳的情况下签名的(这是一个非常重要的必要条件),所以选择了第二个选项.

The first option, is not viable, because the PDF is signed without a timestamp (this is a very important requisite), so the second option is chosen.

这是我们的代码,首先我们得到签名外观,并计算hash:

This is our code, first, we get the signature appearance, and calculate the hash:

PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();

PdfStamper stamper = PdfStamper.createSignature(reader, baos, '', null, true);
appearance = stamper.getSignatureAppearance();
appearance.setCrypto(null, chain, null, PdfSignatureAppearance.SELF_SIGNED);
appearance.setVisibleSignature("Representant");
cal = Calendar.getInstance();
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.TYPE, PdfName.SIG);
dic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
dic.put(PdfName.SUBFILTER, new PdfName("adbe.pkcs7.detached"));
dic.put(PdfName.M, new PdfDate(cal));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, Integer.valueOf(reservedSpace.intValue() * 2 + 2));
appearance.setCertificationLevel(1);
appearance.preClose(exc);

AbstractChecksum checksum = JacksumAPI.getChecksumInstance("sha1");
checksum.reset();
checksum.update(Utils.streamToByteArray(appearance.getRangeStream()));
hash = checksum.getByteArray();

此时,我们有了文档的哈希码.然后我们将哈希发送到网络服务,我们得到签名的哈希码.

In this point, we have the hash code of the document. Then we send the hash to the webservice, and we get the signed hash code.

最后,我们将签名的哈希值放入 PDF:

Finally, we put the signed hash to the PDF:

byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(signedHash, 0, paddedSig, 0, signedHash.length);

PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic);

byte[] pdf = baos.toByteArray();

此时,我们得到了一个签名的 PDF,但签名无效.Adobe 表示文档自签署后已被更改或损坏".

In this point, we get a PDF signed, but with an invalid signature. Adobe says that "Document has been altered or corrupted since it was signed".

我认为我们在这个过程中犯了一些错误,我们不知道到底是什么原因.

I think that we make something wrong in the process, and we don't know exactly what could be.

我们感谢您在这方面的帮助或替代方法.

We appreciate help on this, or an alternative way to do that.

谢谢.

已编辑

按照 mkl 的建议,我遵循了本书的 4.3.3 部分PDF 文档的数字签名,现在我的代码如下:

As suggested by mkl, I have followed the 4.3.3 section of this book Digital Signatures for PDF documents, and my code now what that follows:

第一部分,当我们计算散列时:

The first part, when we calculate the hash:

PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();

PdfStamper stamper = PdfStamper.createSignature(reader, baos, '');
appearance = stamper.getSignatureAppearance();

appearance.setReason("Test");
appearance.setLocation("A casa de la caputeta");
appearance.setVisibleSignature("TMAQ-TSR[0].Pagina1[0].DadesSignatura[0].Representant[0]");
appearance.setCertificate(chain[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(reservedSpace.intValue() * 2 + 2));
appearance.preClose(exc);

ExternalDigest externalDigest = new ExternalDigest()
{
    public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
    {
        return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
    }
};

sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
cal = Calendar.getInstance();

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);

hashPdf = new String(Base64.encode(sh));

在第二部分,我们得到签名的哈希值,并将其放入 PDF 中:

And in the second part, we get the signed hash, and we put that into the PDF:

sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(encodedSign, 0, paddedSig, 0, encodedSign.length);

PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

appearance.close(dic2);

byte[] pdf = baos.toByteArray();

现在,Adobe 引发了内部加密库错误.错误代码:0x2726,当我们尝试验证签名时.

Now, Adobe raises a Internal Cryptographic library error. Error Code: 0x2726, when we try to validate the signature.

推荐答案

经过多次调试,终于找到问题所在.

After much debugging, we finally found the problem.

出于某种神秘的原因,生成文档散列的方法被执行了两次,使第一个散列(我们用来发送到服务)无效.

For some mysterious reason, the method that generates the hash of the document, was executed twice, invalidating the first hash (which we use to send to the service).

对代码进行重构后,原始代码运行正常.

After a refactoring work of the code, the original code worked correctly.

非常感谢所有帮助过我的人,尤其是 mkl.

Very thanks to all people that help me, especially mkl.

这篇关于使用外部服务和 iText 签署 PDF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆