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

使用PDFBox 2.0.34调用外部API签名PDF后提示文档被篡改

问题分析与解决方案

你的核心问题在于错误对接了外部签名结果与CMS签名构造流程:外部API已返回文档内容哈希的ECDSA签名,但后续用BouncyCastle生成CMS签名时,没有正确复用该签名结果,导致最终签名的哈希匹配逻辑与Adobe验证要求不符,触发“文档自签名后已被更改或损坏”的错误。

关键错误点

  1. 自定义的ContentSigner违背了BouncyCastle的设计逻辑:CMSSignedDataGenerator.generate会重新计算文档内容哈希并写入ContentSigner的输出流,但你的实现直接返回外部签名值,导致签名值对应的哈希与CMS结构中记录的哈希不匹配。
  2. 无需手动实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:07:10