使用iText5加密的AES-256 PDF文本可被轻易读取,如何加密需自定义解密
解决自定义AES加密PDF内容,仅允许自定义程序解密的问题
你遇到的核心问题是iText 5默认的PDF加密是遵循PDF官方标准的,Adobe阅读器等标准工具会自动识别并处理这种加密(哪怕你可能不小心设置了空密码或开放权限)。如果想要让只有你的自定义程序才能读取文本,不能依赖PDF标准加密,得自己对PDF的内容流做自定义加密处理。
为什么当前方法无效?
当你用iText的setEncryption方法加密PDF时,本质是给PDF套了一层标准的加密外壳,完全符合PDF规范中定义的加密算法(比如AES-256)。所有合规的PDF阅读器都内置了这套标准解密逻辑,所以只要能通过权限验证,就能正常解析内容,这也是为什么任何人都能轻松读取的原因。
正确的实现思路
我们需要绕过PDF标准加密体系,直接对PDF内部的页面内容流进行自定义AES加密。这样标准阅读器打开时会看到乱码(因为无法识别你的自定义加密逻辑),只有你的程序读取内容流后,用对应的AES密钥解密才能还原文本。
具体步骤(结合iText 5代码)
- 读取原PDF,获取每个页面的内容流字节数据
- 用自定义AES算法加密这些内容流字节
- 将加密后的内容流替换回PDF页面
- 保存修改后的PDF(此时标准阅读器无法正常解析内容)
- 编写自定义解密程序,读取加密后的PDF内容流,用相同AES密钥解密还原文本
示例代码实现
加密部分代码
import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.*; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.FileOutputStream; import java.io.IOException; import java.security.Key; public class CustomPdfEncryptor { public static final String SRC = "/2016.pdf"; public static final String DEST = "/enc.pdf"; // 自定义AES密钥(实际使用时请妥善保管,建议从安全配置读取) // 密钥长度:16位=AES-128,24位=AES-192,32位=AES-256 private static final String AES_KEY = "MySecureAESKey12345678"; public static void main(String[] args) throws IOException, DocumentException { try { // 初始化AES加密器 Key secretKey = new SecretKeySpec(AES_KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); // 读取原PDF并准备修改 PdfReader reader = new PdfReader(SRC); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(DEST)); // 遍历所有页面,加密内容流 int totalPages = reader.getNumberOfPages(); for (int i = 1; i <= totalPages; i++) { // 获取当前页面的原始内容流字节 byte[] contentBytes = reader.getPageContent(i); // 加密内容流 byte[] encryptedBytes = cipher.doFinal(contentBytes); // 替换原页面内容为加密后的字节 PdfContentByte contentByte = stamper.getOverContent(i); contentByte.reset(); contentByte.addRawBytes(encryptedBytes); } stamper.close(); reader.close(); System.out.println("自定义加密PDF生成完成"); } catch (Exception e) { e.printStackTrace(); } } }
解密部分代码(自定义程序专用)
import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.parser.PdfTextExtractor; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.Key; public class CustomPdfDecryptor { public static final String SRC = "/enc.pdf"; private static final String AES_KEY = "MySecureAESKey12345678"; // 和加密时完全一致的密钥 public static void main(String[] args) throws IOException { try { // 初始化AES解密器 Key secretKey = new SecretKeySpec(AES_KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); PdfReader reader = new PdfReader(SRC); int totalPages = reader.getNumberOfPages(); for (int i = 1; i <= totalPages; i++) { // 获取加密后的页面内容流 byte[] encryptedBytes = reader.getPageContent(i); // 解密内容流 byte[] decryptedBytes = cipher.doFinal(encryptedBytes); // 将解密后的PDF语法内容转为纯文本 String rawContent = new String(decryptedBytes); // 可选:用iText的文本提取工具解析纯文本 String plainText = PdfTextExtractor.getTextFromPage(new PdfReader(rawContent.getBytes()), i); System.out.println("第" + i + "页解密后的纯文本:"); System.out.println(plainText); } reader.close(); } catch (Exception e) { e.printStackTrace(); } } }
关键注意事项
- 密钥安全:AES密钥是解密的核心,必须妥善保管,避免硬编码在代码中(建议从环境变量或加密配置文件读取)。
- 内容流特性:PDF内容流是PDF语法指令集合,不是纯文本,解密后需要用
PdfTextExtractor等工具才能提取出可读文本。 - 资源加密扩展:上述代码仅加密页面内容流,PDF中的字体、图片等资源未加密。如果需要加密这些资源,需额外处理PDF的资源字典数据。
- 预期效果:加密后的PDF在标准阅读器中会显示乱码或空白,这是正常现象,只有你的自定义解密程序才能正确解析内容。
内容的提问来源于stack exchange,提问作者desperado06




