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 制作者和读者来说,正确处理此功能似乎绝非易事。