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

使用PDFBox实现PDF签名时,如何确保各环节签名时间一致?

问题

我正在使用PDFBox实现带外部TSA(时间戳授权中心)的PDF数字签名,操作步骤如下:

  1. 创建PDDocument和PDSignature实例;
  2. 根据需求为签名域添加外观;
  3. 配置PDSignature的sub-filter、signDate等参数;
  4. 将PDSignature添加到PDDocument;
  5. 生成PDF哈希并完成签名;
  6. 使用签名摘要获取时间戳令牌,将其作为未签名属性添加;
  7. 生成输出PDF。

我需要在步骤2设置签名外观的日期,步骤3设置PDSignature.signDate,但时间戳令牌要到步骤6才能获取。据我了解,步骤6后无法修改PDSignature.signDate和签名外观的日期。我的需求是让PDF的未签名属性、PDSignature.signDate以及签名外观中的签名时间保持一致,请问是否有办法实现这一点?

解决方案

核心思路是先获取TSA时间戳,再基于该时间戳完成签名外观和PDSignature的配置,调整原有步骤顺序即可实现三者一致:

  1. 提前获取TSA时间戳

    • 先生成一个用于请求TSA的临时哈希(可以用固定测试字符串生成,或提前计算文档的预签名哈希),向TSA服务器请求时间戳令牌;
    • 解析令牌提取出标准的SigningTime时间值,作为后续统一使用的时间戳。
  2. 基于TSA时间戳配置签名参数与外观

    • 直接用提取到的时间戳设置PDSignature.setSignDate();
    • 绘制签名外观时,将该时间戳作为签名文本中的日期显示(通过PDAppearanceContentStream绘制文本时直接引用这个时间值)。
  3. 执行常规签名流程

    • 把配置好的PDSignature添加到文档;
    • 生成正式PDF哈希并完成签名;
    • 将提前获取的TSA令牌作为未签名属性添加到签名数据中。

关键代码示例

// 1. 提前获取并解析TSA时间戳
TSAClient tsaClient = new TSAClient(new URL("你的TSA地址"), "用户名", "密码", 60);
// 生成临时哈希(或提前计算文档预签名哈希)
byte[] tempHash = MessageDigest.getInstance("SHA-256").digest("temp-data".getBytes());
byte[] tsaToken = tsaClient.getTimeStampToken(tempHash);

// 解析令牌中的时间戳
CMSSignedData signedData = new CMSSignedData(tsaToken);
SignerInformation signer = (SignerInformation) signedData.getSignerInfos().getSigners().iterator().next();
Attribute signingTimeAttr = signer.getSignedAttributes().get(new ASN1ObjectIdentifier("1.2.840.113549.1.9.5"));
Date tsaDate = SigningTime.getInstance(signingTimeAttr.getAttrValues().getObjectAt(0)).getDate();

// 2. 配置PDSignature和签名外观
PDSignature signature = new PDSignature();
signature.setSignDate(tsaDate);
signature.setSubFilter(COSName.getPDFName("adbe.pkcs7.detached"));

// 创建签名外观并绘制统一时间戳
PDPage targetPage = document.getPage(0);
PDSignatureField signatureField = new PDSignatureField(document);
signatureField.setValue(signature);
signatureField.setPage(targetPage);

PDAppearanceStream appearanceStream = new PDAppearanceStream(document);
appearanceStream.setBBox(new PDRectangle(100, 100, 300, 50));
try (PDAppearanceContentStream cs = new PDAppearanceContentStream(document, appearanceStream)) {
    cs.beginText();
    cs.setFont(PDType1Font.HELVETICA, 12);
    cs.newLineAtOffset(110, 120);
    cs.showText("签名时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tsaDate));
    cs.endText();
}
PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary();
appearanceDict.setNormalAppearance(appearanceStream);
signatureField.setAppearance(appearanceDict);
document.getDocumentCatalog().getAcroForm().getFields().add(signatureField);

// 3. 完成签名流程
document.addSignature(signature);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(new FileOutputStream("输出文件.pdf"));
byte[] docHash = MessageDigest.getInstance("SHA-256").digest(externalSigning.getContent());

// 私钥签名生成签名值(替换为你的签名逻辑)
PrivateKey privateKey = ...;
byte[] signatureValue = Signature.getInstance("SHA256withRSA").sign(privateKey, docHash);

// 将TSA令牌作为未签名属性整合到签名数据
ASN1EncodableVector unsignedAttrs = new ASN1EncodableVector();
unsignedAttrs.add(new Attribute(new ASN1ObjectIdentifier("1.2.840.113549.1.9.16.2.14"), new DERSet(new DEROctetString(tsaToken))));
// 组装包含未签名属性的最终签名数据(省略具体组装逻辑,可参考PDFBox官方外部签名示例)
byte[] finalSignedData = assembleSignedData(docHash, signatureValue, unsignedAttrs);

externalSigning.setSignature(finalSignedData);

注意事项

  • 若担心临时哈希不符合TSA服务器要求,可以先执行原步骤5的文档哈希计算(不签名),用这个真实的文档哈希请求TSA,更合规;
  • 确保签名外观的日期格式与PDF阅读器默认显示格式一致,避免视觉差异;
  • 提前获取的TSA令牌可以直接复用为未签名属性,不会影响签名的合法性。

内容的提问来源于stack exchange,提问作者Qazazazaz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 13:28:45