使用PDFBox 2.0.34调用外部API签名PDF后提示文档被篡改
问题分析与解决方案
你的核心问题在于错误对接了外部签名结果与CMS签名构造流程:外部API已返回文档内容哈希的ECDSA签名,但后续用BouncyCastle生成CMS签名时,没有正确复用该签名结果,导致最终签名的哈希匹配逻辑与Adobe验证要求不符,触发“文档自签名后已被更改或损坏”的错误。
关键错误点
- 自定义的
ContentSigner违背了BouncyCastle的设计逻辑:CMSSignedDataGenerator.generate会重新计算文档内容哈希并写入ContentSigner的输出流,但你的实现直接返回外部签名值,导致签名值对应的哈希与CMS结构中记录的哈希不匹配。 - 无需手动实现
ContentSigner——因为你没有私钥,而是直接拿到了最终签名结果,应直接基于该结果构造符合CMS规范的签名结构。
修复后的代码实现
替换原代码中从byte[] derSignature = Base64.decodeBase64(signedData2);到externalSigning.setSignature(signedData.getEncoded());的部分,改为以下逻辑:
byte[] derSignature = Base64.decodeBase64(signedData2); BouncyCastleProvider bcProvider = new BouncyCastleProvider(); // 定义算法标识符:需与外部API使用的算法完全一致 AlgorithmIdentifier digestAlgId = new DefaultDigestAlgorithmIdentifierFinder().find("SHA-256"); AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withECDSA"); // 构造证书持有者实例 JcaX509CertificateHolder certHolder = new JcaX509CertificateHolder(signerCertificate); // 构造SignerInfo生成器:直接复用外部返回的签名值 SignerInfoGeneratorBuilder sigInfoBuilder = new SignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().setProvider(bcProvider).build()); SignerInfoGenerator sigInfoGen = sigInfoBuilder.build( new ContentSigner() { @Override public OutputStream getOutputStream() { // 无需写入数据,直接返回空流 return new ByteArrayOutputStream(); } @Override public byte[] getSignature() { // 返回外部API提供的DER格式签名 return derSignature; } @Override public AlgorithmIdentifier getAlgorithmIdentifier() { return sigAlgId; } }, certHolder); // 生成完整的CMS签名数据 CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); cmsGenerator.addSignerInfoGenerator(sigInfoGen); cmsGenerator.addCertificates(new JcaCertStore(Collections.singletonList(signerCertificate))); CMSTypedData cmsData = new CMSProcessableByteArray(contentBytes); CMSSignedData signedData = cmsGenerator.generate(cmsData, false); // 将CMS签名写入PDF externalSigning.setSignature(signedData.getEncoded());
额外校验要点
- 确认外部API使用的哈希算法为SHA-256,与你代码中
DigestUtils.sha256Hex(contentBytes)的哈希算法一致,若API使用其他算法(如SHA-1),需同步修改代码中的算法标识符。 - 验证外部返回的DER签名有效性:用本地加载的证书校验签名是否对应
contentBytes的SHA-256哈希,排除API返回错误签名的可能。 - 确保PDFBox与BouncyCastle版本兼容:PDFBox 2.0.34建议搭配BouncyCastle 1.70及以上版本。
内容的提问来源于stack exchange,提问作者bluebloodedboy
相关产品推荐
相关产品推荐

