PDFBox 1.8.10:填写并签名PDF会生成无效签名

Dan*_*ldt 6 java pdf sign pdf-form pdfbox

我在PDF文档中填写(以编程方式)表单(AcroPdf),然后在文档中签名.我从doc.pdf开始,使用PDFBox的setFields.java示例创建doc_filled.pdf.然后我签署doc_filled.pdf,使用一些代码创建doc?filled_signed.pdf,基于签名示例并在Acrobat Reader中打开pdf.输入的Field数据可见,签名面板告诉我

"此签名中包含的格式或信息存在错误(签名字节数组无效)"

到目前为止,我知道:

  • 单独应用的签名代码(即直接创建一些doc_signed.pdf)会创建一个有效的签名
  • "隐形签名",可见签名和可见签名存在的问题被添加到现有签名字段中.
  • 问题甚至发生,如果我没有填写表格,但只打开并保存,即:

    PDDocument doc = PDDocument.load(new File("doc.pdf"));
    doc.save(new File("doc_filled.pdf"));
    doc.close();
    
    Run Code Online (Sandbox Code Playgroud)

足以打破后来应用的签名代码.

另一方面,如果我使用相同的doc.pdf,在Adobe中手动输入字段的值,则签名代码会生成有效的签名.

我究竟做错了什么?

更新:

@mkl要求我提供文件,我正在谈论(我目前没有足够的声誉,将所有文件发布为链接,抱歉给您带来不便):

  • odc.pdf:https://www.dropbox.com/s/ev8x9q48w5l0hof/doc.pdf dl = 0
  • doc_filled.pdf:https://www.dropbox.com/s/fxn4gyneizs1zzb/doc_filled.pdf dl = 0
  • doc_filled_signed.pdf:https://www.dropbox.com/s/xm846sj8f9kiga9/doc_filled_signed.pdf dl = 0
  • doc_filled_and_signed.pdf:https://www.dropbox.com/s/5jftje6ke87jedr/doc_filled_and_signed.pdf dl = 0

最后一个是通过一次性使用签署和填写文档来创建的

    doc.saveIncremental(); 
Run Code Online (Sandbox Code Playgroud)

正如我已经在评论中写的那样

    setNeedToBeUpdate(true);
Run Code Online (Sandbox Code Playgroud)

但似乎缺少了.参考@mkl的第二条评论,我发现了这个问题:使用PDFBOX生成的PDF中无法正确显示保存的文本字段值,这也包括某些输入的文本未显示.申请时,我先试了一下

    setBoolean(COSName.getPDFName("NeedAppearances"), true); 
Run Code Online (Sandbox Code Playgroud)

到字段和表单的字典,然后显示字段上下文,但签名最终没有添加.我仍然需要进一步研究.

更新: 故事在这里继续:PDFBox 1.8.10:填写并签署文档,再次填充失败

mkl*_*mkl 3

OP最初问题的原因,即用PDFBox加载他的PDF(用于表单填写)然后保存后,这个新的PDF无法使用PDFBox签名代码成功签名,已经在这个答案中详细解释,简而言之:

  • 定期保存文档时,PDFBox 使用交叉引用表来执行此操作。

    • 如果要定期保存的文档是从具有交叉引用流的 PDF 加载的,则交叉引用流字典的所有条目都将保存在预告片字典中。
  • 在应用签名的过程中保存文档时,PDFBox 会创建增量更新;由于此类增量更新要求更新使用与原始修订版相同类型的交叉引用,因此 PDFBox 在这种情况下尝试使用相同的技术。

    • 为了识别最初使用的技术,PDFBox 会查看已加载预告片或交叉引用流字典的文档表示形式中字典的类型条目:是否存在值为XRef的类型条目(为交叉引用流指定) ,假设是一个流,否则是一个表。

doc.pdf因此,在 OP 的原始 PDF具有交叉引用流的情况下:

  • 加载并填写表格后,定期保存文档,即使用交叉引用表,但所有以前的交叉引用流条目(其中 Type 都会复制到预告片。( doc_filled.pdf)

  • 使用交叉引用表加载此保存的 PDF 进行签名后,将使用增量更新再次保存它。PDFBox 假定(由于类型预告片条目)现有文件具有交叉引用流,因此也在增量更新结束时使用交叉引用流。( doc_filled_signed.pdf)

  • 因此,最终填写并签名的 PDF 有两个修订版,内部修订版带有交叉引用表,外部修订版带有交叉引用流。

  • 由于这是无效的,Adobe Reader 在加载 PDF 时会在其内部文档表示中修复此问题。修复更改文档字节。这样,Adobe Reader眼中的签名就被破坏了。

  • 大多数其他签名验证器不会尝试进行此类修复,而是按原样检查文档的签名。他们成功验证了签名。

上面引用的答案也提供了一些解决此问题的方法:

  • 答:加载用于填写表格的 PDF 后,请从预告片中删除“类型”条目,然后再定期保存。如果签名应用于此文件,PDFBox 将采用交叉引用表(因为不存在误导性的类型条目。因此,签名增量更新将有效。

  • B:也使用增量更新来保存表单填写更改,无论是在单独运行中还是在与签名相同的运行中。这也会导致有效的增量更新。

一般来说,我会建议后一个选项,因为如果 PDFBox 保存例程彼此兼容,前一个选项可能会损坏。

但不幸的是,后一个选项需要将添加和更改的对象标记为已更新,包括文档目录中的路径。如果这是不可能的或者至少太麻烦,那么第一个选项可能是更好的选择。


在当前的情况下,OP尝试了后一种选择(doc_filled_and_signed.pdf):

目前,只有选择文本框时,文本框的内容才可见(Acrobat reader 和 Preview 具有相同的行为)。我标记了 PDField、其所有父项、AcroForm、目录以及显示它的页面。

他将更改的字段标记为已更新,但未将 PDFBox 在设置表单字段值时自动生成的关联外观流标记为已更新。

因此,在结果 PDF 文件中,该字段具有新值,但具有旧的空外观流。仅当单击该字段时,Adobe Reader 才会根据编辑值创建新外观。

因此,OP还必须标记新的正常外观流(表单字段字典包含引用字典的条目AP,其中N引用正常外观流)。或者(如果查找更改或添加的条目变得太麻烦)他可能会尝试其他选项。