使用Bouncy Castle和PDFBox在Java中验证PDF签名 [英] Verifying PDF Signature in Java using Bouncy Castle and PDFBox

查看:153
本文介绍了使用Bouncy Castle和PDFBox在Java中验证PDF签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试验证Java中经过数字签名的PDF文档.

I am trying to verify digitally signed PDF document in Java.

我使用Apache PDFBox 2.0.6获取签名和已签名的原始PDF,然后使用Bouncy Castle验证分离的签名(计算原始文件的哈希值,使用签名者的公共身份来验证签名键并比较结果).

I'm using Apache PDFBox 2.0.6 to get the signature and the original PDF that was signed, then I'm using Bouncy Castle to verify detached signature(calculate the hash of the original file, verify the signature using signer's public key and compare the results).

我阅读了本文,并尝试使用以下代码获取签名字节和原始PDF字节:

I read this article and tried to get the signature bytes and the original PDF bytes using this code:

PDDocument doc = PDDocument.load(signedPDF);
    byte[] origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
    byte[] signature = doc.getSignatureDictionaries().get(0).getContents(signedPDF);

但是,当我将origPDF保存到文件时,我注意到它仍然具有签名原始PDF所没有的签名字段.另外,保存origPDF的大小为21 kb,而原始PDF的大小为15 kb.这可能是因为签名字段.

But, when I save the origPDF to a file I notice that it still has the signature field that the original PDF that was signed didn't have. Also, the size of the save origPDF is 21 kb, while the size of the original PDF was 15 kb. That's probably because of the signature fields.

但是,当我尝试像这样从origPDF中剥离签名字段时:

However, when I try to strip signature fields from the origPDF like this:

public byte[] stripCryptoSig(byte[] signedPDF) throws IOException {

    PDDocument pdDoc = PDDocument.load(signedPDF);
    PDDocumentCatalog catalog = pdDoc.getDocumentCatalog();
    PDAcroForm form = catalog.getAcroForm();
    List<PDField> acroFormFields = form.getFields();
    for (PDField field: acroFormFields) {
        if (field.getFieldType().equalsIgnoreCase("Sig")) {
            System.out.println("START removing Sign Flags");
            field.setReadOnly(true);
            field.setRequired(false);
            field.setNoExport(true);
            System.out.println("END removing Sign Flags");

            /*System.out.println("START flattenning field");            
            field.getAcroForm().flatten();
            field.getAcroForm().refreshAppearances();
            System.out.println("END flattenning field");
            */
            field.getAcroForm().refreshAppearances();
        }
    }

我收到以下警告:

警告:无效的字典,找到:"[",但预期:在偏移量15756处为"/"

WARNING: Invalid dictionary, found: '[' but expected: '/' at offset 15756

警告:尚未生成签名字段的外观-您需要手动生成/更新

WARNING: Appearance generation for signature fields not yet implemented - you need to generate/update that manually

而且,当我在Acrobat中打开PDF时,签名字段消失了,但是我看到了签名的图像,该签名曾经作为PDF页面的一部分.这很奇怪,因为我认为我通过使用byte [] origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);

And, when I open the PDF in Acrobat the signature field is gone, but I see an image of the signature where the signature used to be as part of the PDF page. This is weird since I thought I removed the signature completely by using byte[] origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);

顺便说一句,我在origPDF上调用了stripCryptoSig(byte [] signedPDF)函数,所以这不是一个错误.

Btw, I call stripCryptoSig(byte[] signedPDF) function on origPDF, so that's not a mistake.

当我尝试使用充气城堡验证签名时,出现以下消息异常: message-digest属性值与计算值不匹配

When I try to verify the signature using bouncy castle I get an exception with the message: message-digest attribute value does not match calculated value

我想这是因为签名的原始PDF和使用doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);从PDFBox获得的PDF不一样.

I guess this is because the original PDF that was signed and the PDF I get from PDFBox using doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF); isn't the same.

这是我的充气城堡验证码:

Here is my bouncy castle verification code:

private SignatureInfo verifySig(byte[] signedData, boolean attached) throws OperatorCreationException, CertificateException, CMSException, IOException {

    SignatureInfo signatureInfo = new SignatureInfo();
    CMSSignedData cmsSignedData;

    if (attached) {
        cmsSignedData = new CMSSignedData(signedData);
    }

    else {
        PDFUtils pdfUtils = new PDFUtils();
        pdfUtils.init(signedData);
        signedData = pdfUtils.getSignature(signedData);
        byte[] sig = pdfUtils.getSignedContent(signedData);
        cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(signedData), sig);
    }

    SignerInformationStore sis = cmsSignedData.getSignerInfos();
    Collection signers = sis.getSigners();
    Store certStore = cmsSignedData.getCertificates();
    Iterator it = signers.iterator();
    signatureInfo.setValid(false);
    while (it.hasNext()) {
        SignerInformation signer = (SignerInformation) it.next();
        Collection certCollection = certStore.getMatches(signer.getSID());

        Iterator certIt = certCollection.iterator();
        X509CertificateHolder cert = (X509CertificateHolder) certIt.next();

        if(signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert))){

            signatureInfo.setValid(true);

            if (attached) {
                CMSProcessableByteArray userData = (CMSProcessableByteArray) cmsSignedData.getSignedContent();
                signatureInfo.setSignedDoc((byte[]) userData.getContent());
            }

            else {
                signatureInfo.setSignedDoc(signedData);
            }


            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            String signedOnDate = "null";
            String validFromDate = "null";
            String validToDate = "null";

            Date signedOn = this.getSignatureDate(signer);
            Date validFrom = cert.getNotBefore();
            Date validTo = cert.getNotAfter();

            if(signedOn != null) {
                signedOnDate = sdf.format(signedOn);
            }
            if(validFrom != null) {
                validFromDate = sdf.format(validFrom);
            }
            if(validTo != null) {
                validToDate = sdf.format(validTo);
            }

            DefaultAlgorithmNameFinder algNameFinder = new DefaultAlgorithmNameFinder();

            signatureInfo.setSignedBy(IETFUtils.valueToString(cert.getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
            signatureInfo.setSignedOn(signedOn);
            signatureInfo.setIssuer(IETFUtils.valueToString(cert.getIssuer().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
            signatureInfo.setValidFrom(validFrom);
            signatureInfo.setValidTo(validTo);
            signatureInfo.setVersion(String.valueOf(cert.getVersion()));
            signatureInfo.setSignatureAlg(algNameFinder.getAlgorithmName(signer.getDigestAlgorithmID()) + " WTIH " + algNameFinder.getAlgorithmName(cert.getSubjectPublicKeyInfo().getAlgorithmId()));

            /*signatureInfo.put("Signed by", IETFUtils.valueToString(cert.getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
            signatureInfo.put("Signed on", signedOnDate);
            signatureInfo.put("Issuer", IETFUtils.valueToString(cert.getIssuer().getRDNs(BCStyle.CN)[0].getFirst().getValue()));
            signatureInfo.put("Valid from", validFromDate);
            signatureInfo.put("Valid to", validToDate);
            signatureInfo.put("Version", "V" + String.valueOf(cert.getVersion()));
            signatureInfo.put("Signature algorithm", algNameFinder.getAlgorithmName(signer.getDigestAlgorithmID()) + " WTIH " + algNameFinder.getAlgorithmName(cert.getSubjectPublicKeyInfo().getAlgorithmId()));*/

            break;
        }
    }

    return signatureInfo;

}

推荐答案

在我的情况下,我设置签名和signedData的代码中有错误.我不小心交换了这些值.

In my case there was an error in the code where I set the signature and signedData. I accidentally swappped the values.

所以,而不是:

signedData = pdfUtils.getSignature(signedData);
byte[] sig = pdfUtils.getSignedContent(signedData);

应该是:

byte[] sig = pdfUtils.getSignature(signedData);
signedData = pdfUtils.getSignedContent(signedData); 

现在,它正在工作.我使用它进行测试的文件是使用adbe.pkcs7.detached签名的.但是,如果使用其他签名方法,将无法正常工作.

Now, it's working. The file I was testing it with, was signed using adbe.pkcs7.detached. However, it wouldn't work if other signing methonds were used.

因此,感谢@Tilman Hausherr向我指出ShowSignature.java示例. 这就是签名验证的方式.

So, thanks to @Tilman Hausherr for pointing me to the ShowSignature.java example. That's how signature verification should be done.

而且,也感谢@mkl的详细说明.

And, also thanks to @mkl for detailed explanation.

我现在理解,当创建签名时,将添加签名字段,并根据该新值计算哈希值.这就是验证有效的原因.您不需要没有签名字段的原始PDF.

I now understand that when a signature is created signature fields are added and hash is calculated from that new value. That's why the verification is working. You don't need the original PDF without the signature fields.

这篇关于使用Bouncy Castle和PDFBox在Java中验证PDF签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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