签名 PDF - 内存消耗

Signing PDF - memory consumption

我尝试了一些基于 iText v1 或 v2 的数字 PDF 签名实用程序,发现似乎整个 PDF 都加载到内存中(对于 60M PDF 进程可能占用多达 300-400MB 的内存)。

最近的 iText 版本可以在不将 PDF 加载到内存的情况下对 PDF 进行签名吗?

更新

我用 itextpdf 5.5.6

测试了 B运行o 的例子

但是内存消耗还是太大了。我尝试签署 100M 文件(它是带有嵌入式附件的 PDF),峰值内存约为 325M。当然,它比没有临时文件的 540M 好,但还不够好 (((.

最大 32K 文件。内存为 65M(我猜这是 JVM 和 java 代码本身)

内存是用 /usr/bin/time -v java ....

测量的

我用 -Xmx100m 限制了 Java 内存,但它因内存不足而崩溃:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.itextpdf.text.pdf.PdfReader.getStreamBytesRaw(PdfReader.java:2576) at com.itextpdf.text.pdf.PdfReader.getStreamBytesRaw(PdfReader.java:2615) at com.itextpdf.text.pdf.PRStream.toPdf(PRStream.java:230) at com.itextpdf.text.pdf.PdfIndirectObject.writeTo(PdfIndirectObject.java:158) at com.itextpdf.text.pdf.PdfWriter$PdfBody.write(PdfWriter.java:420) at com.itextpdf.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:398) at com.itextpdf.text.pdf.PdfWriter.addToBody(PdfWriter.java:887) at com.itextpdf.text.pdf.PdfStamperImp.close(PdfStamperImp.java:412) at com.itextpdf.text.pdf.PdfStamperImp.close(PdfStamperImp.java:386) at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1316) at com.itextpdf.text.pdf.security.MakeSignature.signDetached(MakeSignature.java:140)

代码是:

public static byte[] getStreamBytesRaw(final PRStream stream, final RandomAccessFileOrArray file) throws IOException {
        PdfReader reader = stream.getReader();
        byte b[];
        if (stream.getOffset() < 0)
            b = stream.getBytes();
        else {
      ----> b = new byte[stream.getLength()];
            file.readFully(b);

我在调试器中看到流类型是 EmbeddedFile 并且长度是 100M - 所以整个嵌入文件正在被读入内存。

更新 - 创建大 PDF

很难共享 100M 文件 )),但这里是创建顺序:

  1. 运行 dd if=/dev/urandom of=file.bin bs=1048000 count=100
  2. 转到http://blog.didierstevens.com/programs/pdf-tools/ and take http://didierstevens.com/files/software/make-pdf_V0_1_6.zip
  3. 解压并运行python make-pdf-embedded.py file.bin file.pdf

给你)

我应该注意使用 /dev/urandom 很重要。 /dev/zero 创建只有 100K 大小的压缩 PDF。

无论如何,如果有必要获取我的文件,我已经在服务器上创建了 50M 的文件 - http://50mpdf.tk/50m.pdf

请下载免费电子书 Digital Signatures for PDF documents。 2.2.4 节标题为 "Signing large PDF files"。它解释了如何使用临时文件而不是将文件保存在内存中来签署文档:

// Creating the reader and the stamper
PdfReader reader = new PdfReader(filepath, null, true);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper =
    PdfStamper.createSignature(reader, os, '[=10=]', new File(tmp));
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain,
    null, null, null, 0, subfilter);

您看到我们如何创建 PdfStamper 实例了吗?我们在 createSignature() 方法中添加一个 File object 作为额外参数。此代码示例中的 tmp 变量可以是特定文件或目录的路径。如果选择了一个目录,iText 将在该目录中创建一个具有唯一名称的文件。

如果对临时文件使用 createSignature() 方法,则可以使用 OutputStreamos 值),即 null。在这种情况下,临时文件将作为实际的目标文件。如果您的目标是在文件系统上存储签名文件,这是一个很好的做法。如果 OutputStream 不是 null,iText 总是会在签名完成后尝试删除临时文件。

请不要再使用 iText v1 或 v2。使用这些版本创建的签名类型已过时,iText 版本也是如此(另请参阅 https://whosebug.com/questions/25696851/can-itext-2-1-7-or-earlier-can-be-used-commercially)。

在签署 PDF 时,iText 会使用相关的内存量

  • 将整个未签名的 PDF 读入内存,除非在 部分模式下使用 PdfReader
  • 在内存中创建签名文件,除非使用 PdfStamper 配置为使用临时文件;和
  • 在将未签名数据复制到待签名文件时,将整个单独的 PDF 对象(例如包含嵌入文件的流)读取到内存中,除非在 追加模式下使用 PdfStamper.

例如签署 OP 提供的示例 50 MB 文件需要

  • 关于 -Xmx240m 如果既不使用追加模式,也不使用临时文件,也不使用部分模式;
  • 关于-Xmx81m如果使用临时文件而不是附加模式,部分模式没有区别;
  • 关于 -Xmx7m 如果使用附加模式和临时文件,部分模式没有区别。

部分模式在后面的情况下没有区别的原因是,即使在非部分模式下,PdfReader 似乎也不会读取流 contents在初始化期间。由于示例文件主要由单个大流的内容组成,因此在初始化过程中读取或不读取的少数对象不会产生影响,尤其是即使在部分模式下 PdfReader 也会读取某些对象并将其保留在内存中反映了全局文档结构,例如页面树。

你可以在这里找到我的测试例程:CreateSignature.java。我 运行 它在 64 位 MS Windows Java 8 上使用 iText 5.5.7-SNAPSHOT(在这种情况下应该与 5.5.6 版本没有区别)。

因此,为了便于记忆的签名,请使用@Bruno 代码的这种变体:

// Creating the reader and the stamper
PdfReader reader = new PdfReader(filepath, null, true);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper =
    PdfStamper.createSignature(reader, os, '[=10=]', new File(tmp), true);
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain,
    null, null, null, 0, subfilter);