当前位置: 首页 > 知识库问答 >
问题:

利用Bouncy Castle和PDFBox验证Java中的PDF签名

濮佑运
2023-03-14

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

PDDocument doc = PDDocument.load(signedPDF);
    byte[] origPDF = doc.getSignatureDictionaries().get(0).getSignedContent(signedPDF);
    byte[] signature = doc.getSignatureDictionaries().get(0).getContents(signedPDF);
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处找到:“[”,但应为:“/”

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

当我尝试使用bouncy castle验证签名时,我得到一个消息异常:message-digest属性值不匹配计算值

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

这里是我的bouncy castle验证码:

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;

}

共有1个答案

岳泉
2023-03-14

您似乎对getSignedContent方法和PDF签名有误解。

我正在使用Apache PDFBox 2.0.6获取签名和签名的原始PDF

如果“已签名的原始PDF”指的是进入签名过程之前的PDF,那么任务的第二部分对于一般已签名的PDF是不可能的。

但另一方面,这可能还意味着还会发生以下一些变化:

  • 可以从头创建新签名字段;
  • 可以向文档中添加一个用于签名可视化的附加页面;
  • 可以将额外的签名可视化(非活动图像或实际的签名表单字段小工具)添加到每个页面;
  • 可能会创建表单字段缺少的外观;
  • 签名应用程序可以将其名称添加到元数据条目中,作为文档处理器,上次更改的日期和时间可以更新为签名时间;
  • 如果预先存在空签名字段,则该字段的字段锁字典指示的表单字段可设置为只读;
  • 等PP

如果文档之前没有签名,则这些添加不需要作为增量更新添加,而是可以将所有对象(改变或未改变)重新排序,重新编号,间接对象可以变为直接对象,反之亦然,可以丢弃未使用的对象,可以将重复的对象减少为单个对象,可以将表单字段的字体变为只读的字形,等等

只有在准备好的PDF中,才会创建实际的签名,并将其嵌入到签名值字典中留下的空隙中。

如果应用呼叫

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

对于已签名的文档,origpdf包含签名值字典中除gap以外的已签名文档的字节,signature包含gap的(十六进制解码)内容。

因此origpdf特别包含在准备过程中所做的所有更改;因此,将其称为orig具有极大的误导性。

此外,由于原先为签名容器保留的空隙丢失了,很可能这些字节实际上不再形成有效的PDF:PDF包含指向每个PDF对象的起始偏移量(从文档开始)的交叉引用;由于空隙丢失了,它以前的位置移动后的字节和现在去那里的偏移是错误的。

因此,您的origpdf仅包含签名字节的集合,这可能与您认为的原始文件非常不同。

您的verifysig完全忽略签名字段值字典的子筛选器。根据该值,使用getcontents检索的签名字节可能具有完全不同的内容。

因此,如果没有您签名的PDF,进一步审查该方法是没有意义的。

 类似资料:
  • 问题内容: 我正在尝试验证Java中经过数字签名的PDF文档。 我使用Apache PDFBox 2.0.6获取签名和已签名的原始PDF,然后使用Bouncy Castle验证分离的签名(计算原始文件的哈希,使用签名者的公钥验证签名并进行比较结果)。 我阅读了这篇文章,并尝试使用以下代码获取签名字节和原始PDF字节: 但是,当我将origPDF保存到文件中时,我注意到它仍然具有签名原始PDF所没有

  • 我想使用BouncyCastle解析和验证OpenPGP分离签名。签名如下所示: 下面是我如何尝试在Kotlin中创建CMSSignedData: 我应该如何使用BouncyCastle验证这种签名?

  • 这是接口签名 这是类签名者 我用java和bouncycastle创建了证书和密钥对,我现在不知道是问题还是我做错了什么?

  • 我应该如何更改A方法?任何想法都是好的。提前谢谢。

  • 问题内容: 我已经测试了一种解决方案,可以验证ECDSA签名如何从EC公共密钥字节中获取PublicKey对象?,该签名可以完美地处理给定的数据。 这是数据: 这是代码(显示 true ): 当我将签名和数据更改为来自已实施系统的示例输入时,就会出现我的问题: 新数据输出此错误: 我认为问题出在 安全消息 附带的签名上,因为: 密钥对的长度和格式与示例相同。并且是正确的,因为它来自对消息进行签名的

  • 我试图找到用pdfbox版本2签署pdf的例子。x、 在bouncycastle中,我看到的只是pdfbox版本1.8.9 https://github.com/mkl-public/testarea-pdfbox1/blob/master/src/main/java/mkl/testarea/pdfbox1/sign/CreateSignature.java 这适用于pdfbox 1.8.9,但