
本文详解 itext 5 中实现 pdf 多重签名的关键原理与实操要点,指出“仅显示最新签名”的根本原因在于签名后 pdf 被意外扁平化(flattening),导致历史签名字段丢失;并提供规避方案、代码优化建议及验证方法。
本文详解 itext 5 中实现 pdf 多重签名的关键原理与实操要点,指出“仅显示最新签名”的根本原因在于签名后 pdf 被意外扁平化(flattening),导致历史签名字段丢失;并提供规避方案、代码优化建议及验证方法。
在使用 iText 5 对 PDF 进行数字签名时,若希望同一份文档支持多个独立、可验证的签名(例如多方协同签署),必须严格遵循 PDF 签名的增量更新(incremental update)机制。你遇到的“第二次签名后仅显示最新签名”问题,并非代码逻辑错误,而是签名后的 PDF 文件被外部工具(如 LibreOffice Draw 7.5)进行了扁平化处理——该操作会将已签名的表单域(包括签名外观和签名字典)永久渲染为静态图形或删除签名结构,使原始签名元数据不可恢复。
✅ 正确实现多重签名的核心原则
-
禁止任何中间修改或“另存为”操作:每次签名必须基于上一次签名生成的 原始二进制流(即未被 PDF 查看器、编辑器或转换工具处理过的
.pdf文件),且不能调用stamper.setFormFlattening(true)或类似破坏性操作。 -
使用增量保存(默认行为):iText 5 的
PdfStamper.createSignature(..., true)中第三个参数true表示启用 append mode(追加模式),它会在文件末尾添加新签名数据,保留原有签名结构。这是多重签名的技术基础。 -
确保签名字段名称唯一:若需可视化多个签名域,每次调用
sap.setVisibleSignature(...)时应指定不同的签名域名称(第三个参数fieldName不为空且不重复),例如"signature1"、"signature2";否则后续签名会覆盖前一个字段定义。
? 示例:安全的多签名流程(伪代码 + 关键修正)
// 第一次签名(输入:原始 unsigned.pdf)
byte[] signed1 = signPdf(originalBytes, cert1, "signature1");
// 第二次签名(输入:必须是 signed1,而非其打开再保存后的副本!)
byte[] signed2 = signPdf(signed1, cert2, "signature2"); // ← 关键:直接复用上一轮输出字节数组
// 签名方法需支持复用已有签名PDF作为输入
private byte[] signPdf(byte[] inputPdf, X509Certificate cert, String fieldName)
throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(inputPdf); // ← 输入必须是纯二进制,无额外处理
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 注意:createSignature 第三个参数 'true' 启用增量写入
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("Approved by Dept A");
sap.setLocation("Beijing");
// 指定唯一字段名,避免覆盖
sap.setVisibleSignature(new Rectangle(100, 700, 250, 650), 1, fieldName);
sap.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new CustomExternalSignature(/*...*/);
MakeSignature.signDetached(sap, digest, signature,
new Certificate[]{cert}, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
stamper.close(); // 自动以增量方式写入
reader.close();
return os.toByteArray();
}
⚠️ 常见陷阱与验证建议
- ❌ 错误做法:用 Acrobat / Foxit / LibreOffice 打开已签名 PDF → 点击“保存”或“另存为” → 再用此文件二次签名。这些操作极大概率触发扁平化。
-
✅ 验证是否保留多重签名:
- 使用
pdfinfo -meta signed.pdf检查/Signature条目数量; - 用 Adobe Acrobat 打开 → “签名”面板 → 应列出全部签名项;
- 编程验证:
PdfReader.getAcroFields().getSignatureNames()返回签名字段名列表。
- 使用
-
依赖项注意:确保
bouncycastle版本兼容(推荐bcprov-jdk15on-1.68+),并正确注册Security.addProvider(new BouncyCastleProvider())。
? 总结
多重签名不是“重复调用签名方法”即可达成,而是一套对 PDF 文件生命周期的精细化管控:从原始文件出发,全程保持二进制完整性,仅通过 iText 的增量追加机制叠加签名数据。只要跳过任何中间渲染、导出或编辑环节,你的 PDF 就能自然承载无限次合规数字签名,并在标准阅读器中完整显示与验证。










