如何使用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
相关产品推荐
相关产品推荐

