You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PDFBox 2.0.24为文档添加多个可见签章图章同时保留有效的数字签名

如何使用PDFBox 2.0.24为文档添加多个可见签章图章同时保留有效的数字签名

嘿,我来帮你搞定用PDFBox 2.0.24实现多可见签章还保留原有签名有效性的问题!首先得明确一个核心逻辑:PDF的数字签名是对文档内容的哈希校验,直接改原有内容肯定会把签名搞废,所以必须用PDF规范里的增量更新玩法,所有新内容都追加到文档末尾,绝不碰之前的已签名部分。

下面是具体的实现思路和步骤,结合代码示例给你讲清楚:

一、核心原则先记牢

  • 已签名的PDF绝对不能修改原有内容,所有新添加的签章、文本、图片都必须放在增量更新段里,这样原有签名的哈希校验才能保持有效。
  • 每个可见签章都对应一个独立的签名域,不能重复使用同一个域名称,不然会出现显示异常或者签名失效的问题。

二、具体实现步骤

1. 准备好基础已签名PDF

先把你已经完成第一次签名的PDF文档准备好,假设咱们叫它signed.pdf,这是咱们操作的基础。

2. 编写代码添加新的可见签章

下面的代码示例会帮你在已签名PDF上添加一个新的可见签章,同时保证原有签名有效,你可以重复这个流程添加更多签章:

// 加载已签名的PDF文档
try (PDDocument document = PDDocument.load(new File("signed.pdf"))) {
    // 1. 创建新的签名域,每个签章必须有唯一的域名称
    PDSignatureField signatureField = new PDSignatureField(document);
    signatureField.setFieldName("Signature2"); // 换成你自己的唯一标识
    
    // 2. 设置签章在页面上的位置和所属页面
    PDPage targetPage = document.getPage(1); // 这里选第2页(索引从0开始)
    // 定义签章的矩形区域:x坐标, y坐标, 宽度, 高度(PDF坐标原点在左下角)
    PDRectangle stampRect = new PDRectangle(400, 50, 200, 100);
    signatureField.setWidgetRectangle(stampRect);
    signatureField.getWidgets().get(0).setPage(targetPage);
    
    // 3. 把签名域添加到文档的表单中
    PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
    if (acroForm == null) {
        // 如果文档没有表单,就新建一个
        acroForm = new PDAcroForm(document);
        document.getDocumentCatalog().setAcroForm(acroForm);
    }
    acroForm.getFields().add(signatureField);
    
    // 4. 绘制签章的可见外观(就是用户在页面上看到的图章内容)
    PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary();
    PDAppearanceStream appearanceStream = new PDAppearanceStream(document);
    appearanceDict.setNormalAppearance(appearanceStream);
    signatureField.getWidgets().get(0).setAppearance(appearanceDict);
    
    // 在外观流里画你想要的签章内容:边框、文字、图片都可以
    try (PDPageContentStream contentStream = new PDPageContentStream(document, appearanceStream)) {
        // 画一个黑色边框
        contentStream.addRect(0, 0, stampRect.getWidth(), stampRect.getHeight());
        contentStream.setStrokingColor(Color.BLACK);
        contentStream.stroke();
        
        // 添加签名相关文本
        contentStream.beginText();
        contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
        contentStream.newLineAtOffset(10, 50); // 文本起始位置
        contentStream.showText("张三 已完成签名");
        contentStream.newLineAtOffset(0, -20);
        contentStream.showText("签署日期:2024-05-20");
        contentStream.endText();
        
        // 如果需要添加印章图片,取消下面的注释,替换成你的图片路径
        // PDImageXObject stampImage = PDImageXObject.createFromFile("company-stamp.png", document);
        // contentStream.drawImage(stampImage, 10, 10, 180, 80);
    }
    
    // 5. 准备数字签名所需的密钥和证书(换成你自己的密钥库信息)
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new FileInputStream("your-keystore.p12"), "your-password".toCharArray());
    String alias = keyStore.aliases().nextElement();
    PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, "your-password".toCharArray());
    Certificate[] certChain = keyStore.getCertificateChain(alias);
    
    // 6. 创建签名对象,设置签名的基本信息
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("张三");
    signature.setLocation("北京市");
    signature.setReason("文档审批通过");
    signature.setSignDate(Calendar.getInstance());
    
    // 把签名和签名域关联起来
    signatureField.setValue(signature);
    
    // 7. 关键!用增量更新保存文档,保证原有签名不失效
    File outputFile = new File("multi-signed.pdf");
    try (FileOutputStream fos = new FileOutputStream(outputFile)) {
        document.saveIncremental(fos, new ExternalSigningSupport() {
            @Override
            public OutputStream getContentOutputStream(OutputStream os) throws IOException {
                return os;
            }
            
            @Override
            public byte[] sign(byte[] content) throws IOException {
                // 这里对文档内容的哈希进行签名,用你自己的签名逻辑
                try {
                    Signature sig = Signature.getInstance("SHA256withRSA");
                    sig.initSign(privateKey);
                    sig.update(content);
                    return sig.sign();
                } catch (Exception e) {
                    throw new IOException("签名失败", e);
                }
            }
        });
    }
} catch (Exception e) {
    e.printStackTrace();
}

3. 重复流程添加更多签章

如果你需要添加第三个、第四个签章,只需要重复上面的流程:新建签名域、设置位置、绘制外观、关联签名、增量保存就行。每次操作都是基于上一次的增量更新文档,这样所有签名都会保持有效。

三、避坑指南

  • 绝对要用saveIncremental保存:普通的save方法会重写整个文档,直接破坏原有签名的哈希校验,导致原有签名失效。saveIncremental是把新内容追加到文档末尾,完全不碰之前的已签名部分,这是保证原有签名有效的核心。
  • 签名域名称必须唯一:每个签章对应一个唯一的域名称,不然会出现签章覆盖或者显示异常的问题。
  • 外观绘制要在PDAppearanceStream中:所有可见签章的内容都要画在外观流里,不能直接画在页面上,不然会修改原有文档内容,破坏签名。

备注:内容来源于stack exchange,提问作者dtr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 16:00:33