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

查看:295
本文介绍了使用外部服务和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.

此Web服务提供两个选项,发送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.

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

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, '\0', 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();

在这一点上,我们有文档的哈希码。然后我们将哈希发送到web服务,我们得到签名的哈希码。

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, '\0');
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天全站免登陆