签名 PDF - 内存消耗
Signing PDF - memory consumption
我尝试了一些基于 iText v1 或 v2 的数字 PDF 签名实用程序,发现似乎整个 PDF 都加载到内存中(对于 60M PDF 进程可能占用多达 300-400MB 的内存)。
最近的 iText 版本可以在不将 PDF 加载到内存的情况下对 PDF 进行签名吗?
更新
我用 itextpdf 5.5.6
测试了 B运行o 的例子
- PdfReader 构造函数无关紧要 - 它可以是 (src) 或 (src, null, true),或者
(src, null, false) - 结果相同。
- 重要的是 createSignature 中的新文件 (tmp)。
但是内存消耗还是太大了。我尝试签署 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 文件 )),但这里是创建顺序:
- 运行
dd if=/dev/urandom of=file.bin bs=1048000 count=100
- 转到http://blog.didierstevens.com/programs/pdf-tools/ and take http://didierstevens.com/files/software/make-pdf_V0_1_6.zip
- 解压并运行
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()
方法,则可以使用 OutputStream
(os
值),即 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);
我尝试了一些基于 iText v1 或 v2 的数字 PDF 签名实用程序,发现似乎整个 PDF 都加载到内存中(对于 60M PDF 进程可能占用多达 300-400MB 的内存)。
最近的 iText 版本可以在不将 PDF 加载到内存的情况下对 PDF 进行签名吗?
更新
我用 itextpdf 5.5.6
测试了 B运行o 的例子- PdfReader 构造函数无关紧要 - 它可以是 (src) 或 (src, null, true),或者 (src, null, false) - 结果相同。
- 重要的是 createSignature 中的新文件 (tmp)。
但是内存消耗还是太大了。我尝试签署 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 文件 )),但这里是创建顺序:
- 运行
dd if=/dev/urandom of=file.bin bs=1048000 count=100
- 转到http://blog.didierstevens.com/programs/pdf-tools/ and take http://didierstevens.com/files/software/make-pdf_V0_1_6.zip
- 解压并运行
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()
方法,则可以使用 OutputStream
(os
值),即 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);