【问题标题】:Adding new signature by reatining existing unsigned signature fields using PDFBox通过使用 PDFBox 重新处理现有未签名的签名字段来添加新签名
【发布时间】:2023-02-06 13:42:30
【问题描述】:

我想签署已经包含签名字段的 PDF。我需要添加新的签名字段以保留现有的未签名签名字段。签署此类 PDF 后,我看到代码添加的新签名字段始终无效。说“文件已被更改”。

以下代码用于计算文档的哈希值:

private DocumentSignatureStructure createSignatureStructureAndComputeHash(byte[] inputFile, File tempFile,
                                                                              SignatureProperties sigProperties)
            throws IOException, NoSuchAlgorithmException {

        try (FileOutputStream fos = new FileOutputStream(tempFile);
             PDDocument doc = PDDocument.load(inputFile);
             SignatureOptions signatureOptions = new SignatureOptions();) {

            signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
            signatureOptions.setPage(sigProperties.getPage() - 1);
            if (sigProperties.isVisibleSignature()) {
                PDRectangle rect = createSignatureRectangle(doc, sigProperties);
                signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, rect, sigProperties));
            }


            PDSignature signature = new PDSignature();
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            signature.setSignDate(Calendar.getInstance());
            doc.addSignature(signature, signatureOptions);
            ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);

            MessageDigest digest = MessageDigest.getInstance(sigProperties.getHashAlgorithm().getAlgoName());
            byte[] hashBytes = digest.digest(IOUtils.toByteArray(externalSigning.getContent()));
            String base64Hash = Base64.toBase64String(hashBytes);
            externalSigning.setSignature(new byte[0]);
            int offset = signature.getByteRange()[1] + 1;
            IOUtils.closeQuietly(signatureOptions);
            return DocumentSignatureStructure.builder().offset(offset)
                    .hashValue(base64Hash)
                    .build();
        }
    }

嵌入签名代码:

byte[] originalDocumentByte = docBlob.getBytes(1L, (int) docBlob.length());
            File file = new File(getTempFolderPath(), getTempFileName("signed"));
            try (FileOutputStream fos = new FileOutputStream(file);) {
                fos.write(originalDocumentByte);
            }
            try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
                raf.seek(documentSignatureStructure.getOffset());
                raf.write(Hex.getBytes(Base64.decode(encodedSignature)));
            }
            Blob signedAndLtvBlob;
            try (PDDocument doc = PDDocument.load(file);
                 FileOutputStream fos = new FileOutputStream(file);
                 FileInputStream fis = new FileInputStream(file)) {
                if (createDss) {
                    log.info("Adding revocation information to DSS dictionary of PDF");
                    makeLtv(doc, revocationData);
                }
                doc.saveIncremental(fos);
            }

它不适用于上面的代码。

在谷歌上搜索,发现很少有 COSObject 'NeedToBeUpdated' 标志需要设置为 true 的解决方案。 在上面的代码中添加新的签名字段之前添加了下面的代码块。

//..
if (sigProperties.isVisibleSignature()) {
                PDRectangle rect = createSignatureRectangle(doc, sigProperties);
                signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, rect, sigProperties));
            }

            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
            COSDictionary catalogDictionary = doc.getDocumentCatalog().getCOSObject();
            catalogDictionary.setNeedToBeUpdated(true);
            COSDictionary acroFormDictionary = (COSDictionary) catalogDictionary.getDictionaryObject(COSName.ACRO_FORM);
            acroFormDictionary.setNeedToBeUpdated(true);
            COSArray array = (COSArray) acroFormDictionary.getDictionaryObject(COSName.FIELDS);
            array.setNeedToBeUpdated(true);
            for (PDField field : acroForm.getFieldTree()) {
                if (field instanceof PDSignatureField) {
                    COSDictionary fieldDictionary = field.getCOSObject();
                    COSDictionary dictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.AP);
                    dictionary.setNeedToBeUpdated(true);
                    COSStream stream = (COSStream) dictionary.getDictionaryObject(COSName.N);
                    stream.setNeedToBeUpdated(true);
                    while (fieldDictionary != null)
                    {
                        fieldDictionary.setNeedToBeUpdated(true);
                        fieldDictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.PARENT);
                    }
                }
            }
            
            PDSignature signature = new PDSignature();
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
//..

即使这样也行不通。

生成的 PDF 显示签名无效:

用于使用签名字段签名的 PDF:

我在这里缺少的是什么?

PDF文件:https://drive.google.com/file/d/1-vu9_WIfFo198v6AxoBMxCuyX1rE2FOS/view?usp=share_link

签名PDF(无效):https://drive.google.com/file/d/1DD0aKVkonH9a_CfGrj9mACe6DBt4Ijsj/view?usp=share_link

【问题讨论】:

  • 请分享一个像这样签名的示例 PDF 以供分析。
  • @mkl 共享 PDF
  • 谢谢,但是你能分享一下PDF吗签名无效进行分析。
  • @mkl 添加了签名 PDF
  • 谢谢@mkl。我在错误的地方寻找解决方案,因为它与流用于读取和写入文件数据的方式有关。签名现在有效。谢谢。

标签: java pdfbox sign


【解决方案1】:

某些东西确实损坏了您的文件,而且显然不是您显示的代码。

将您的签名文件“Formulier DSS-01 - DC+QV Onboarding Checklist-signed.pdf”与您的原始文件“Formulier DSS-01 - DC+QV Onboarding Checklist.pdf”进行比较,可以看出前者不仅仅是后者具有增量正如人们所期望的那样更新,但该文件部分中的某些区域已被零覆盖。

有问题的区域来自 0x2c000-0x2cfff 和 0x40000-0x40fff。

通过将原始文件的这些区域的内容复制到签名文件中解决此问题后,Adobe Acrobat 会积极验证签名。

因此,您应该尝试找出是什么将文件中的这两个 4KB 区域归零。

【讨论】:

    【解决方案2】:

    我正在寻找生成文档散列的代码中的错误。 但问题实际上在于文件流用于编写 CMSSignedData 和添加 DSS 字典以制作签名 PDF LTV 的方式。

    使用相同的文件进行加载和写入导致文件的某些部分被零覆盖。

    下面一段用于嵌入外部签名的代码解决了我的问题。

    Blob docBlob = documentSignatureStructure.getReadyDocument();                      
    byte[] originalDocumentByte = docBlob.getBytes(1L, (int) docBlob.length());        
    File signedFile = new File(getTempFolderPath(), getTempFileName("signed_"));       
    try (FileOutputStream fos = new FileOutputStream(signedFile)) {                    
        fos.write(originalDocumentByte);                                               
    }                                                                                  
    try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw")) {              
        raf.seek(documentSignatureStructure.getOffset());                              
        raf.write(Hex.getBytes(Base64.decode(encodedSignature)));                      
    }                                                                                  
                                                                                       
    File signedLtvFile = new File(getTempFolderPath(), getTempFileName("signedLtv_")); 
    try (PDDocument doc = PDDocument.load(signedFile);                                 
         FileOutputStream fos = new FileOutputStream(signedLtvFile)) {                 
        if (createDss) {                                                               
            log.info("Adding revocation information to DSS dictionary of PDF");        
            makeLtv(doc, revocationData);                                              
            doc.saveIncremental(fos);                                                  
        }                                                                              
    }                                                                                  
                                                                                       
    Blob signedAndLtvBlob;                                                             
    try (FileInputStream fis = new FileInputStream(signedLtvFile)) {                   
        signedAndLtvBlob = new javax.sql.rowset.serial.SerialBlob(fis.readAllBytes()); 
    }                                                                                  
                                                                                       
    Files.delete(signedFile.toPath());                                                 
    log.debug("temp signed file {} deleted successfully", signedFile.getName());       
    Files.delete(signedLtvFile.toPath());                                              
    log.debug("temp ltv signed file {} deleted successfully", signedLtvFile.getName());
    return signedAndLtvBlob;                                                           

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 2021-12-06
      • 2015-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多