如何在 PDF 中实现多次数字签名(iText 5 教程)

星瑶酱_9003

星瑶酱_9003

2026-09-11

896人浏览

原创

如何在 PDF 中实现多次数字签名(iText 5 教程)

使用 itext 5 对 pdf 进行多次数字签名时,常见问题在于后续签名会覆盖前序签名——根本原因并非代码逻辑错误,而是 pdf 在签名之间被外部工具(如 libreoffice、acrobat“保存”或某些 pdf 库)意外扁平化(flattening),导致原有签名字段丢失。本文详解正确实现多签名的关键原则与实操步骤。

使用 itext 5 对 pdf 进行多次数字签名时,常见问题在于后续签名会覆盖前序签名——根本原因并非代码逻辑错误,而是 pdf 在签名之间被外部工具(如 libreoffice、acrobat“保存”或某些 pdf 库)意外扁平化(flattening),导致原有签名字段丢失。本文详解正确实现多签名的关键原则与实操步骤。

在 PDF 标准中,多次签名是完全支持的(即“增量更新 + 独立签名字段”),但前提是:每次签名操作都必须基于上一次签名后未经修改的原始字节流(即保留 PDF 的增量更新结构),且签名字段(/Sig 字典)不能被任何中间处理流程删除或固化(flatting)。你当前代码本身逻辑正确(使用 PdfStamper.createSignature(..., true) 启用了追加模式),问题出在签名流程之外的数据流转环节。

✅ 正确实现多签名的三大前提

  1. 输入 PDF 必须是“洁净”的原始文件:未被 LibreOffice、浏览器 PDF 预览器、旧版 Acrobat 或其他 PDF 工具“另存为”或“优化”。这类操作常触发字段扁平化,使已有签名仅剩视觉外观,失去数字签名结构。
  2. 每次签名必须基于前次签名输出的原始字节流:禁止将已签名 PDF 先写入磁盘再读取、禁止用 PdfReader 重新解析已签名文件后再次 PdfStamper(除非明确启用增量模式并确保未重写对象)。
  3. 签名字段需独立命名且位置不重叠:避免多个签名共用同一字段名(如默认 "Signature1"),否则后签会覆盖前签。应动态生成唯一字段名(如 "Signature_20241105_001")。

? 修复后的核心代码示例(支持多次签名)

// ✅ 关键:始终基于上一次签名的 byte[] 直接构造 PdfReader,不经过任何中间转换
public byte[] signPdf(byte[] pdfBytes, byte[] img, String certBase64, String fieldName, 
                      HttpHeaders headers, SignReq req, AuthorizationReq authReq) 
        throws IOException, DocumentException, GeneralSecurityException {

    PdfReader reader = new PdfReader(pdfBytes); // ← 输入必须是上一轮输出的原始字节数组
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    // ⚠️ 必须启用增量模式(true 第二个参数),否则会重写整个 PDF,破坏前序签名
    PdfStamper stamper = PdfStamper.createSignature(reader, outputStream, '', null, true);

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) cf.generateCertificate(
        new ByteArrayInputStream(Base64.getDecoder().decode(certBase64.replaceAll("\s", "")))
    );

    PdfSignatureAppearance sap = stamper.getSignatureAppearance();
    sap.setReason("Approved by Finance Dept");
    sap.setLocation("Ho Chi Minh City");
    sap.setVisibleSignature(new Rectangle(100, 700, 250, 650), 1, fieldName); // ← 唯一字段名!
    sap.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

    ExternalDigest digest = new BouncyCastleDigest();
    ExternalSignature signature = new CustomExternalSignature(headers, req, authReq, signatureServices);

    MakeSignature.signDetached(sap, digest, signature, new Certificate[]{cert}, 
                               null, null, null, 0, MakeSignature.CryptoStandard.CMS);

    stamper.close(); // 自动执行增量写入
    reader.close();
    return outputStream.toByteArray(); // ← 输出原始字节,供下一次签名直接使用
}

⚠️ 常见陷阱与规避建议

  • ❌ 错误做法
    // 将签名后 PDF 保存为文件 → 用 File.open() 再读取 → 被系统或编辑器悄悄扁平化  
    Files.write(Paths.get("signed.pdf"), signedBytes);  
    byte[] nextInput = Files.readAllBytes(Paths.get("signed.pdf")); // ❌ 风险极高!
  • ✅ 正确链式调用
    byte[] original = files.get(0).getBytes();
    byte[] firstSigned = signPdf(original, ... , "Sig_A", ...);
    byte[] secondSigned = signPdf(firstSigned, ... , "Sig_B", ...); // ← 直接传入字节数组
    byte[] thirdSigned = signPdf(secondSigned, ... , "Sig_C", ...);
  • 验证签名完整性:使用 Adobe Acrobat 或 pdfsig(Poppler 工具)检查 PDF 是否包含多个 /Sig 字典:
    pdfsig signed_multiple.pdf
    # 输出应显示 >1 个签名条目,且状态均为 "Signature is VALID"

? 总结

iText 5 完全支持 PDF 多签名,其本质是利用 PDF 的增量更新机制(Incremental Update)在文件末尾追加新签名对象,而非覆盖原内容。失败的根本原因几乎总是外部工具对 PDF 的“无意识破坏”。因此,严格管控输入源、杜绝中间文件落地、确保字段命名唯一、全程使用内存字节数组流转,是实现可靠多签名的黄金准则。若仍遇到问题,请用 pdfinfo -metapdfsig 检查 PDF 元数据与签名结构,定位扁平化发生环节。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

8717

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

5942

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

5332

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

964

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

808

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1136

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2309

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19651

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1055

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.1万人学习