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

iText数字签名破坏PDF/A2b

姜嘉赐
2023-03-14

使用itext v5对文档进行数字签名时。5.11 PDF/A-2b文档被破坏——这意味着它们不再作为PDF/A文档有效。违反以下规则:https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

在上面的链接中,它指定摘要无效,因此我也给你一个代码段,在使用iText签署pdf文档时处理计算摘要:

        // Make the digest
        InputStream data;
        try {

            data = signatureAppearance.getRangeStream();
        } catch (IOException e) {
            String message = "MessageDigest error for signature input, type: IOException";
            signLogger.logError(message, e);
            throw new CustomException(message, e);
        }
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA1");

        } catch (NoSuchAlgorithmException ex) {
            String message = "MessageDigest error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] buf = new byte[8192];
        int n;
        try {
            while ((n = data.read(buf)) > 0) {
                messageDigest.update(buf, 0, n);
            }
        } catch (IOException ex) {
            String message = "MessageDigest update error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] hash = messageDigest.digest();
        // If we add a time stamp:
        // Create the signature
        PdfPKCS7 sgn;
        try {

            sgn = new PdfPKCS7(key, chain, configuration.getSignCertificate().getSignatureHashAlgorithm().value() , null, new BouncyCastleDigest(), false);
        } catch (InvalidKeyException ex) {
            String message = "Certificate PDF sign error for signature input, type: InvalidKeyException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchProviderException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchProviderException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchAlgorithmException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }catch (Exception ex) {
            String message = "Certificate PDF sign error for signature input, type: Exception";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null,null, MakeSignature.CryptoStandard.CMS);
        try {
            sgn.update(sh, 0, sh.length);
        } catch (java.security.SignatureException ex) {
            String message = "Certificate PDF sign error for signature input, type: SignatureException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] encodedSig = sgn.getEncodedPKCS7(hash);
        if (contentEstimated + 2 < encodedSig.length) {
            String message = "The estimated size for the signature is smaller than the required one. Terminating request..";
            signLogger.log("ERROR", message);
            throw new CustomException(message);
        }
        byte[] paddedSig = new byte[contentEstimated];
        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
        // Replace the contents
        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
        try {
            signatureAppearance.close(dic2);
        } catch (IOException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (DocumentException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: DocumentException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }

对于PDF/A验证,我使用VeraPDF库。

值得一提的是,虽然VeraPDF库报告PDF/A库已损坏,但Adobe Reader验证工具报告PDF/A文档未损坏。

任何帮助都将不胜感激。

共有3个答案

姜嘉荣
2023-03-14

我确实同意veraPDF目前如何检查ByteRange的分析。实际上,它假定文件正好在签名字段后面的%EOF标记处终止。

原因很简单,文档可以由几个人依次签名,并且仍然可以是有效的PDF/A-2B文档,当生成第二个签名时,它会递增地更新包含第一个签名的文件。

因此,如果我们从字面上理解PDF/A-2B要求中的术语文件:

计算文件摘要时,应计算整个文件,包括签名字典,但不包括PDF签名本身。然后,该范围由签名字典的字节数条目表示。

我们永远无法创建具有多个签名的有效PDF/a文件。这显然不是PDF/A-2标准的意图。

PDF文件通常被理解为前导%PDF到后导%EOF之间的字节范围,例如,允许PDF文件作为更大字节流(例如,邮件附件)的一部分。这就是veraPDF实现的基础。

但是,我同意这种方法没有考虑%EOF之后的可选行尾序列。我已经为veraPDF:https://github.com/veraPDF/veraPDF-validation/issues/166创建了相应的问题

它留下了一个有趣的问题:如果文档有更多的签名,第一个签名的有效字节范围是多少?我相信,所有情况下:

  • ByteRange将文件覆盖到下一个%EOF标记

应该被允许。

齐修贤
2023-03-14

如上所述,我们刚刚发布了veraPDF 1.4的热修复补丁,解决了本次讨论中的问题。新版本可供下载:http://downloads.verapdf.org/rel/1.4/verapdf-1.4.5-installer.zip

特别是,iText签名的PDF/A-2文档似乎可以很好地通过veraPDF验证。

谷梁煌
2023-03-14

使用itext v5对文档进行数字签名时。5.11 PDF/A-2b文档被破坏——这意味着它们不再作为PDF/A文档有效。违反以下规则:https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

虽然这确实是veraPDF声称的,但这是错误的;iText创建覆盖整个版本的签名,减去为签名容器保留的空间。

这种不正确的违规检测的原因是veraPDF中的一个错误。

veryPDF版本(基于绿地解析器的版本和基于PDFBox的版本)都试图确定标称字节范围值,并将其与实际值进行比较。这就是它如何确定标称值:

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    pdfSource.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    readWholeBuffer(pdfSource, buffer);
    pdfSource.rewind(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        readWholeBuffer(pdfSource, buffer);
        if (pdfSource.isEOF()) {
            pdfSource.seek(currentOffset + document.getHeaderOffset());
            return pdfSource.length();
        }
        pdfSource.rewind(buffer.length - 1);
    }
    long result = pdfSource.getPosition() + buffer.length - 1;  // offset of byte after 'F'
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    return result - 1;
}

说明:基于PDFBox的SignatureParser类

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    source.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    source.read(buffer);
    source.unread(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        source.read(buffer);
        if (source.isEOF()) {
            source.seek(currentOffset + document.getHeader().getHeaderOffset());
            return source.getStreamLength();
        }
        source.unread(buffer.length - 1);
    }
    long result = source.getOffset() - 1 + buffer.length;   // byte right after 'F'
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    return result - 1;
}

(基于greenfield解析器的签名解析器)

本质上,这两个实现在这里做同样的事情,从签名开始,他们寻找文件结束标记%%EOF的下一个出现,并尝试完成标称字节范围值,以便第二个范围以该标记结束。

这种确定标称有符号字节范围值的方法是错误的,原因有很多:

>

  • 根据PDF/A规范,

    除ISO 32000-1:2008,7.5.5中所述的单个可选行尾标记外,文件最后一个行尾标记后面不能有任何数据。

    因此,直接位于下一个文件结束标记%%EOF之后的偏移量不一定已经是已签名修订的结束,正确的偏移量可能是位于下一个行结束标记之后的偏移量!由于PDF行尾标记可以是单个CR或单个LF或CRLF组合,这意味着veraPDF选择三个可能的偏移量中的一个,并声称它是修订的标称端,因此是有符号字节范围的标称端。

    有可能(即使几乎从未见过)在一次修订中准备一个签名值(以文件结尾标记结束),然后在增量更新中追加一些数据,导致新的修订(以另一个文件结尾标记结束),然后用签名文档(包括新修订)的值填充签名值。

    由于veraPDF使用签名字典之后的下一个文件结尾标记,在这种情况下,veraPDF实际上选择了错误的文件结尾标记。

    在语法上,文件结尾标记%EOF实际上只是一个在PDF/修订版结尾处具有特殊含义的注释,并且在PDF字符串、PDF流数据和PDF交叉引用表之外的PDF中几乎所有地方都允许注释。因此,字节序列%EOF可以在签名值字典和签名修订版的实际结尾之间作为常规注释或字符串或流的非注释内容出现任意次数。

    如果出现这样的情况,veraPDF会选择一个字节序列作为文件的结尾标记,这从来都不是什么事情的结尾。

    此外,除非循环中达到了文件的实际结尾(和pdfSource.length()/source)。getStreamLength()返回),结果显示为关闭1,返回结果-1中的-1与结果的使用不一致。

    我对照veraPDF的当前1.5.0快照版本进行了检查,该版本被标记为:

    • veraPDF-pdfbox验证1.5.4
    • veraPDF验证1.5.2
    • veraPDF解析器1.5.1

    OP提供的示例文档在文件结束标记后有一个LF。由于这一点以及上面提到的off by one问题,veraPDF确定了一个短两个字节的标称有符号字节范围end。

  •  类似资料:
    • 我使用PdfWriter setEncryption对PDF文档进行了加密/解密。一切正常,解密也正常。 当我为数字签名的PDF文档做同样的事情时,我的数字信息与消息一起损坏(SigDict/Contents非法数据) 是否可以在不影响数字签名信息的情况下加密PDF?

    • 我正在尝试向现有的数字签名pdf(认证签名)中添加一个空签名字段。 我有一个工作流,其中许多用户将签署该文档(批准签名),该文档创建时带有“n”个空签名字段,每个用户一个,我们的应用程序首先应用一个不可见的认证签名,然后每个用户可以在各自的字段中签署该文档,但由于工作流中意外的更改,其他用户可能希望签名,因此,我们希望添加相应的空签名字段,然后应用签名。 我试图将空字段(带有单元格事件的表)添加到

    • 我这里需要帮助,有人能帮我吗?

    • 我正在用C#开发一个执行数字签名验证的webserver,以确保pdf文件没有被修改。我使用了iText和iTextSharp。 和我的C#验证码: 在VerifySignature(name)行中;抛出NullReferenceException! 有趣的是,如果我使用C#代码执行签名,我就可以在java中验证它,因为我添加了这些指令:BouncyCastleProvider provider=

    • 问题内容: 如何使用iText签名pdf?我正在通过此LINK进行操作, 但不了解my_private_key.pfx。我真的需要数字签名证书吗?请澄清一下。提前致谢。 问题答案: 您在问题中提到的文档很好。您必须创建数字签名文件。 该链接具有使用PKCS文件和签署PDF文档的工具。它声称使用iText,因此您应该能够理解这些步骤。源代码在这里

    • 我想做多个签名的pdf文件,像在一个工作流。我正在使用下面的代码签署我写的pdf文件,这工作很好。 签署文件 而且上面的代码工作得很好。我的新要求是添加多个签名。是否有任何方法可以重用此代码段。我经历了这,这,这,但没有运气。 除此之外,我尝试的是,创建了空白多个空白签名,并尝试附加签名。但它导致创建损坏的文件。我还尝试使用本链接中提到的方法创建文件。还对PDF文档进行了很好的文档数字签名 nul