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

使用Bouncy Castle 1.70验证无签名属性签名的可行性咨询

Bouncy Castle数字签名验证迁移逻辑的可行性问题

问题背景与代码场景

我在一个使用Bouncy Castle进行数字签名验证的项目中遇到了问题。原有验签方法如下:

private Set<TLName> verifySignature(PDSignature sig, X509Certificate signerCertificate, SignerInformation signerInfo, List<X509Certificate> chain, SignatureInfo signatureInfo) throws OperatorCreationException {
        Set<TLName> emptyTrustedBy = new HashSet<>(TLName.values().length);

        // checking cert validity at signing time
        try {
            signerCertificate.checkValidity(sig.getSignDate().getTime());
        } catch (CertificateExpiredException e) {
            LOG.info("Signature verification failed, certificate is expired.");
            return emptyTrustedBy;
        } catch (CertificateNotYetValidException e) {
            LOG.info("Signature verification failed, certificate is not yet valid.");
            return emptyTrustedBy;
        }

        // check all certificates from chain on trust lists
        Set<TLName> filledTrustedBy = TrustedCertificates.getTrustedByForChain(chain.toArray(new X509Certificate[0]));
        if (filledTrustedBy.isEmpty()) {
            LOG.info("Signature verification failed, certificate not found between our trusted certificates.");
            return emptyTrustedBy;
        }

        // verifying signature with public key
        SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().build(signerCertificate);
        try {
            if (signerInfo.verify(verifier)) {
                signatureInfo.signedBytesUnchanged = true;
                signatureInfo.checksum = DatatypeConverter.printHexBinary(signerInfo.getContentDigest());
                LOG.debug("Signature verification completed, signature verified.");
                return filledTrustedBy; // only here return trustedBy array filled with values
            }
            signatureInfo.checksum = DatatypeConverter.printHexBinary(signerInfo.getContentDigest());
        } catch (CMSException e) {
            LOG.info("Signature verification failed, couldn't verify signature. Exception message: " + e.getMessage());
        }

        return emptyTrustedBy;
    }

之前有额外校验层自行计算校验和供其他组件检查,代码如下:

private void checkSignedBytes(SignerInformation signerInfo, File inFile, int[] byteRange, SignatureInfo signatureInfo) throws NoSuchAlgorithmException, IOException {
        Attribute attribute = signerInfo.getSignedAttributes().get(CMSAttributes.messageDigest);
        DEROctetString sigDigest = (DEROctetString) attribute.getAttrValues().getObjectAt(0);
        byte[] sigDigestBytes = sigDigest.getOctets();

        byte[] contentDigestBytes = Signer.getDigest(inFile, byteRange, signerInfo.getDigestAlgOID());

        signatureInfo.checksum = DatatypeConverter.printHexBinary(sigDigestBytes);
        signatureInfo.signedBytesUnchanged = Arrays.equals(sigDigestBytes, contentDigestBytes);
    }

这个额外校验方法在遇到无签名属性的数字签名时失效。我研究了Bouncy Castle的SignerInformation类中doVerify(...)方法,发现它会处理无签名属性的签名,相关摘要计算和验证逻辑如下:

摘要计算逻辑

if (signedAttributeSet == null)
                    {
                        if (contentVerifier instanceof RawContentVerifier)
                        {
                            content.write(digOut);
                        }
                        else
                        {
                            OutputStream cOut = new TeeOutputStream(digOut, sigOut);

                            content.write(cOut);

                            cOut.close();
                        }
                    }

验证逻辑

if (signedAttributeSet == null && resultDigest != null)
            {
                if (contentVerifier instanceof RawContentVerifier)
                {
                    RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier;

                    if (encName.equals("RSA"))
                    {
                        DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE), resultDigest);

                        return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
                    }

                    return rawVerifier.verify(resultDigest, this.getSignature());
                }
            }

我的疑问

我已经将checkSignedBytes(...)方法的逻辑移到verifySignature(...)方法中,具体修改如下:

if (signerInfo.verify(verifier)) {
                signatureInfo.signedBytesUnchanged = true;
                signatureInfo.checksum = DatatypeConverter.printHexBinary(signerInfo.getContentDigest());
                LOG.debug("Signature verification completed, signature verified.");
                return filledTrustedBy; // only here return trustedBy array filled with values
            }

请问这样处理是否可行?是否会因缺少签名属性而丢失信息?由于其他组件需要这些属性,我必须初始化它们,即使看起来冗余。


回答

可行性结论

这种迁移是完全可行的,且能解决原方法在无签名属性场景下失效的问题。

核心分析

  1. getContentDigest()的兼容性
    Bouncy Castle的SignerInformation.getContentDigest()内部已兼容两种场景:

    • 存在签名属性时,从CMSAttributes.messageDigest属性提取摘要值;
    • 无签名属性时,返回doVerify过程中计算得到的resultDigest(即你贴出的验证逻辑中的值)。
      无论签名是否带属性,该方法都能正确获取对应摘要,不会丢失信息。
  2. signedBytesUnchanged的正确性
    原方法通过对比签名摘要和自行计算的内容摘要判断signedBytesUnchanged,现在在signerInfo.verify(verifier)返回true时设置该值为true是合理的:因为signerInfo.verify已完成签名完整性验证,内部已确认签名对应的内容摘要与实际计算的摘要匹配,此时设置该属性完全可靠。

  3. 其他组件的属性需求
    无需担心冗余初始化问题:

    • 有签名属性的场景,getContentDigest()返回的就是原方法从messageDigest提取的值,与之前的sigDigestBytes完全一致;
    • 无签名属性的场景,返回的是Bouncy Castle验证过程中生成的有效摘要,同样能满足其他组件对checksum的需求;
    • signedBytesUnchanged的取值完全符合原逻辑语义,不会影响依赖该属性的组件。

额外建议

如果需要完全对齐原逻辑语义(比如验签失败时也需设置checksum),当前代码中if (signerInfo.verify(verifier))分支外的signatureInfo.checksum = DatatypeConverter.printHexBinary(signerInfo.getContentDigest());可以保留,确保无论验签成功与否,checksum都能被正确初始化,满足其他组件需求。


内容的提问来源于stack exchange,提问作者Štěpán Vejvoda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 21:19:52