介绍
我在对标记为符合 PDF/A - 3A 的 PDF 文档进行数字签名时遇到问题。使用 PDFBox(最新版本,2.0.24)我最终在 Adobe Acrobat 中得到一个无效签名,而使用 iText7(最新版本)我得到一个有效签名。目标是获得符合 PAdES LTV 的签名。
概述
我的过程如下(使用 PDFBox 和 iText7):
- 我打开 PDF,创建用于签名的哈希(要签名的数据)
- 我打电话给第 3 方服务以取回数字签名
- 在服务响应中,我还获得了需要在 PDF 中嵌入以提高 LTV 质量的 OCSP 和 CRL 内容
- 我在 PDF 中嵌入了签名
- 我将文档保存到内存中,然后重新打开它以嵌入 OCSP 和 CRL
- 我嵌入了 OCSP 和 CRL 项目,创建了各自的 DSS 和 VRI 字典
- 我将 PDF 保存到磁盘
对于 PDFBox,签名代码在此处,而 OCSP/CRL 嵌入代码在此处。对于 iText7,用于签名和 OCSP/CRL 嵌入的代码在这里。
问题
现在,这适用于大多数 PDF 文件,包括多重签名文档。问题在于一个特定的 PDF,它被创建为 PDF/A compliat,级别 3A。
使用 PDFBox,如果我只是嵌入签名并在 Adobe Acrobet 中打开文档,则签名是有效的。如果我还嵌入了 OCSP/CRL 内容,则签名不再有效。Adobe Acrobat 抱怨说:
签名无效:文档自签名后已被更改或损坏。
我还注意到,只需这样做:
document.load(inputStream);
document.save(outputStream);
我破坏了签名。从我的测试来看,实际嵌入并不是问题的真正原因,而只是我在嵌入签名后重新打开 PDF 并将其保存回磁盘的事实。
通过 iText7 使用相同的过程(密钥、证书等),我最终在 Adobe Acrobat 中获得了有效的 LTV 签名。
示例 PDF
样本文件在这里。原件包含未签名的文档,然后有 2 个示例,一个用于 PDFBox(在 Adobe Acrobat 中无效),一个用于 iText7(在 Adobe Acrobat 中有效)。
到目前为止,我的研究表明,在签名嵌入后加载 PDF 时,PDFBox 以某种方式破坏了元素的顺序。它在加载和保存文档时暗示了这个问题,尽管对于所有其他 PDF 我执行相同的过程并且 Adobe Acrobat 不会抱怨签名。
我还尝试使用 PDFBox 2.1.0-SNAPSHOT 和 3.0.0-SNAPSHOT,希望问题与 PDF 中元素的排序有关并且已修复。不过,我得到了相同的结果。
后期编辑 1
请看下面的Later edit 2,这里的Later edit 1 不是个好主意!
根据@mkl 下面接受的答案,问题出在原始 PDF 文件上,其中包含分成几个小节而不是一个小节的交叉引用表。这似乎是由最初生成 PDF 的服务使用的库(Aspose PDF for .NET,版本 21.3 或更早版本)引起的。
似乎适用于我当前代码的一种解决方法如下:
PDDocumentInformation info = pdDocument.getDocumentInformation();
if (info != null && StringUtils.containsIgnoreCase(info.getProducer(), "Aspose")) {
try {
pdDocument.save(inMemoryStream);
pdDocument.close();
pdDocument = PDDocument.load(inMemoryStream.toByteArray());
inMemoryStream.reset();
} catch (Exception e) {
基本上,如果我检测到文档的制作者是 Aspose,我会将文档保存在内存中(通过 PDFBox 的pdDocument.save())并将其加载回来。这可确保交叉引用表正确写入内存,并从那里签名和 OCSP+CRL 嵌入按预期工作,从而在 Adobe Acrobat 中生成有效签名。
后期编辑 2
谢谢@mkl 和@TilmanHausherr,你是对的。假设使用某个库生成的所有文档都必须自动标准化并不是一个好主意,因为现有签名将失效。最后,更好的办法是保持代码不变,并期待一个正确构建的 PDF。修复创建位置的问题。