4

如PDF 32000-1:2008第 7.6.1 节所述,我想创建一个未加密的 pdf 文件,其中包含 iText 的加密嵌入文件:

从 PDF 1.5 开始,嵌入文件可以在其他未加密的文档中加密

但是,以下示例 (iText 7.0.1) 会生成一个带有未加密嵌入文件流的 PDF 文件(关闭压缩以更好地分析生成的 PDF 文件):

             /* cf. 7.6.3.1: Documents in which only file attachments are 
                encrypted shall use the same password as the user and owner password.*/
PdfWriter writer = new PdfWriter(fileName, new WriterProperties()
                         .setStandardEncryption("secret".getBytes(),
                         "secret".getBytes(), EncryptionConstants.ALLOW_PRINTING |
                         EncryptionConstants.ALLOW_MODIFY_ANNOTATIONS,
                         EncryptionConstants.ENCRYPTION_AES_128 |
                         EncryptionConstants.DO_NOT_ENCRYPT_METADATA |
                         EncryptionConstants.EMBEDDED_FILES_ONLY)
                         .setCompressionLevel(CompressionConstants.NO_COMPRESSION));

PdfDocument pdf = new PdfDocument(writer);  

PdfFileSpec fs = PdfFileSpec.createEmbeddedFileSpec(pdf,"attached file".getBytes(),
                         null,"attachment.txt",null,null,null,true);
pdf.addFileAttachment("attachment.txt", fs);

try (Document doc = new Document(pdf)) {
        doc.add(new Paragraph("main file"));
}

此结果似乎与规范说明相反:

如果流的内容嵌入在 PDF 文件中(参见 7.11.4,“嵌入的文件流”),它们应像文件中的任何其他流一样被加密

上述示例生成的 pdf 文件包含 CF 字典中加密嵌入文件流的正确条目:

<</CF<</StdCF<</AuthEvent/EFOpen/CFM/AESV2/Length 16>>>>/EFF/StdCF

规范的表 20 指出:

加密嵌入文件时,一致性写入器应尊重此值,但具有自己的加密过滤器说明符的嵌入文件流除外。

在我们的例子中,流没有自己的 CF 说明符,因此应该使用 AESV2 进行加密。但是,在我们的示例中,流未加密:

4 0 obj
<</Length 13/Params<</ModDate(D:20160930101501+02'00')/Size 13>>/Subtype    /application#2foctet-stream/Type/EmbeddedFile>>stream
attached file
endstream
endobj

这导致以下问题:

  1. 这是 iText 中的错误还是我误解了 PDF 规范?
  2. 如何使用 iText 创建带有加密嵌入文件的未加密 pdf 文件?
  3. 如果这(还)不可能,是否有任何其他免费库或命令行工具可以做到这一点?

PS:Acrobat Reader DC 和 PDF-XChange Viewer 2.5 要求输入密码才能打开附件,而(不符合标准的)阅读器(如 evince)可以毫无问题地打开附件。但这不是我的问题。我的问题不是关于读者行为和可能的道德规范,而是关于 pdf 文件本身及其对规范的遵守情况。

4

1 回答 1

3

2021 年更新:
7.1.16 版终于在其他未加密的 pdf 文档中实现了嵌入文件的加密。
(API 略有变化:在下面对 iText 7 的测试中,删除最后一个参数createEmbeddedFileSpec,使其显示为PdfFileSpec.createEmbeddedFileSpec(pdf,"attached file".getBytes(),null,"attachment.txt",null,null,null);


原始答案

由于我没有得到任何答案,我对 iText 5.5.9 和 iText 7.0.1 进行了更多测试,并得出结论,不加密嵌入式文件流EMBEDDED_FILES_ONLY是新版本 iText 7 中的一个错误。它仅适用于 iText 5 和ENCRYPTION_AES_256,尽管 Acrobat 阅读器发出警告,指出此页面存在错误,并且可能无法正确显示页面。详情见下表:

测试结果总结

以下是使用 iText 5.5.9 生成上表中使用的 pdf 文件的最小、完整和可验证示例的代码...

package pdfencryptef_itext5;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfFileSpecification;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class PDFEncryptEF_iText5 {

    public static void main(String[] args) throws Exception {

        new PDFEncryptEF_iText5().createPDF("iText5_STD128.pdf", PdfWriter.STANDARD_ENCRYPTION_128);
        new PDFEncryptEF_iText5().createPDF("iText5_AES128.pdf", PdfWriter.ENCRYPTION_AES_128);
        new PDFEncryptEF_iText5().createPDF("iText5_AES256.pdf", PdfWriter.ENCRYPTION_AES_256);
        
        Security.addProvider(new BouncyCastleProvider());
        new PDFEncryptEF_iText5().createPDF("iText5_AES128C.pdf", -PdfWriter.ENCRYPTION_AES_128);
        new PDFEncryptEF_iText5().createPDF("iText5_AES256C.pdf", -PdfWriter.ENCRYPTION_AES_256);
        
    }
 
    public void createPDF(String fileName, int encryption  ) throws FileNotFoundException, DocumentException, IOException, CertificateException {
     
        Document document = new Document();
        Document.compress = false;

        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(fileName));
        
        if( encryption >= 0 ){
            writer.setEncryption("secret".getBytes(),"secret".getBytes(), 0,
                   encryption | PdfWriter.EMBEDDED_FILES_ONLY);
        } else {
            Certificate cert = getPublicCertificate("MyCert.cer" );
            writer.setEncryption( new Certificate[] {cert}, new int[] {0}, -encryption  | PdfWriter.EMBEDDED_FILES_ONLY);
        }
        writer.setPdfVersion(PdfWriter.VERSION_1_6);

        document.open();
        
        PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(writer, null, "attachment.txt", "attached file".getBytes(), 0);
        writer.addFileAttachment( fs );
        
        document.add(new Paragraph("main file"));
        document.close(); 

    }
    
     public Certificate getPublicCertificate(String path) throws IOException, CertificateException {
        FileInputStream is = new FileInputStream(path);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
        return cert;
    }
    
}

...和 ​​iText 7.0.1:

package pdfencryptef_itext7;

import com.itextpdf.kernel.pdf.CompressionConstants;
import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class PDFEncryptEF_iText7 {

    public static void main(String[] args) throws Exception {

        new PDFEncryptEF_iText7().createPDF("iText7_STD128.pdf", EncryptionConstants.STANDARD_ENCRYPTION_128);
        new PDFEncryptEF_iText7().createPDF("iText7_AES128.pdf", EncryptionConstants.ENCRYPTION_AES_128);
        new PDFEncryptEF_iText7().createPDF("iText7_AES256.pdf", EncryptionConstants.ENCRYPTION_AES_256);
        
        Security.addProvider(new BouncyCastleProvider());
        new PDFEncryptEF_iText7().createPDF("iText7_AES128C.pdf", -EncryptionConstants.ENCRYPTION_AES_128);
        new PDFEncryptEF_iText7().createPDF("iText7_AES256C.pdf", -EncryptionConstants.ENCRYPTION_AES_256);
    }
 
    public void createPDF(String fileName, int encryption  ) throws FileNotFoundException, IOException, CertificateException{
     
        PdfWriter writer;
        
        if( encryption >= 0 ){
            writer = new PdfWriter(fileName, new WriterProperties().setStandardEncryption("secret".getBytes(),"secret".getBytes(),
                    0,
                    encryption | EncryptionConstants.EMBEDDED_FILES_ONLY)
                    .setPdfVersion(PdfVersion.PDF_1_6));
        } else {
            Certificate cert = getPublicCertificate("MyCert.cer" );
            writer = new PdfWriter(fileName, new WriterProperties().setPublicKeyEncryption( new Certificate[] {cert}, 
                    new int[] {0},
                    -encryption  | EncryptionConstants.EMBEDDED_FILES_ONLY )
                    .setPdfVersion(PdfVersion.PDF_1_6));
        }
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        
        PdfDocument pdf = new PdfDocument(writer);  

        PdfFileSpec fs = PdfFileSpec.createEmbeddedFileSpec(pdf,"attached file".getBytes(),null,"attachment.txt",null,null,null,true);
        pdf.addFileAttachment("attachment.txt", fs);

        try (Document doc = new Document(pdf)) {
            doc.add(new Paragraph("main file"));
        }
    }
    
    public Certificate getPublicCertificate(String path) throws IOException, CertificateException {
        FileInputStream is = new FileInputStream(path);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
        return cert;
    }
    
}

我必须承认,对于 iText 人员至少对我的三个问题中的第一个没有任何反馈,我感到有点失望,但希望 iText 7 的未来版本能够正确处理该EMBEDDED_FILES_ONLY标志。正如测试显示的那样,对于 pdf 制作者和读者来说,正确处理此功能似乎绝非易事。

于 2016-10-27T14:08:52.703 回答