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

如何在不使用DSS的情况下用Bouncy Castle生成PAdES-LT签名(CMSSignedData)

将PAdES-T升级为PAdES-LT的实现方法及撤销信息位置

我现有一段可生成PAdES-B格式CMSSignedData的代码,在将时间戳添加至未签名属性后,该签名已升级为PAdES-T格式。请问如何将其进一步转换为PAdES-LT格式?撤销信息应添加至何处?

原PAdES-B生成代码

private CMSSignedDataGenerator getCmsSignedDataGenerator(ElectronicSignatureModel signatureModel) {
        CMSSignedDataGenerator signGenerator = new CMSSignedDataGenerator();
        signGenerator.addCertificates(new JcaCertStore(signatureModel.getChain()));
        ContentSigner nonSigner = new ContentSigner() {

            @Override
            public byte[] getSignature() {
                return signatureModel.getSignature();
            }

            @Override
            public OutputStream getOutputStream() {
                return new ByteArrayOutputStream();
            }

            @Override
            public AlgorithmIdentifier getAlgorithmIdentifier() {
                return new DefaultSignatureAlgorithmIdentifierFinder()
                        .find(signatureModel.getSigningAlgorithm().getAlgoName());
            }
        };

        JcaSignerInfoGeneratorBuilder signerBuilder = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().build());
        signerBuilder.setDirectSignature(true);
       
        //user certificate
        Certificate userCertHolder = Certificate.getInstance(ASN1Primitive.fromByteArray(
                signatureModel.getChain().get(0).getEncoded()));
        signGenerator.addSignerInfoGenerator(signerBuilder.build(nonSigner, new X509CertificateHolder(userCertHolder)));
        return signGenerator;
    }
CMSSignedDataGenerator signGenerator = getCmsSignedDataGenerator(signatureModel, false);
CMSSignedData cmsSignedData = signGenerator.generate(new CMSProcessableByteArray(signatureModel.getSignature()), false);

实现PAdES-LT的核心要求

PAdES-LT需包含长期可验证的撤销信息(CRL或OCSP响应),确保签名在证书过期后仍能被验证。撤销信息需被签名保护防止篡改,因此优先添加到SignerInfo的已签名属性中。

具体实现步骤

1. 撤销信息的添加位置

  • 推荐方式:已签名属性中的id-aa-ets-revocationValues:直接将CRL/OCSP响应嵌入已签名属性,该属性会被签名覆盖,确保不可篡改,完全符合PAdES-LT的长期验证要求。
  • 补充方式:CMS的CertStore:将CRL添加到证书存储中,但该方式的撤销信息不被签名保护,仅作为辅助验证手段。

2. 修改代码实现

调整签名生成器,添加已签名属性和撤销信息

private CMSSignedDataGenerator getCmsSignedDataGenerator(ElectronicSignatureModel signatureModel, List<X509CRL> crls) throws Exception {
    CMSSignedDataGenerator signGenerator = new CMSSignedDataGenerator();
    
    // 将证书和CRL加入CertStore(可选,作为补充)
    List<ASN1Encodable> certsAndCrls = new ArrayList<>();
    for (Certificate cert : signatureModel.getChain()) {
        certsAndCrls.add(X509CertificateHolder.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded())));
    }
    for (X509CRL crl : crls) {
        certsAndCrls.add(CRL.getInstance(ASN1Primitive.fromByteArray(crl.getEncoded())));
    }
    signGenerator.addCertificates(new JcaCertStore(certsAndCrls));

    ContentSigner nonSigner = new ContentSigner() {
        @Override
        public byte[] getSignature() {
            return signatureModel.getSignature();
        }

        @Override
        public OutputStream getOutputStream() {
            return new ByteArrayOutputStream();
        }

        @Override
        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return new DefaultSignatureAlgorithmIdentifierFinder()
                    .find(signatureModel.getSigningAlgorithm().getAlgoName());
        }
    };

    JcaSignerInfoGeneratorBuilder signerBuilder = new JcaSignerInfoGeneratorBuilder(
            new JcaDigestCalculatorProviderBuilder().build());
    
    // 构建已签名属性集合
    ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
    
    // 添加消息摘要属性:必须计算待签名数据的摘要
    String digestAlgoName = signatureModel.getSigningAlgorithm().getAlgoName().replace("SHA256withRSA", "SHA-256");
    MessageDigest md = MessageDigest.getInstance(digestAlgoName);
    byte[] messageDigest = md.digest(signatureModel.getSignature());
    signedAttrs.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest))));
    
    // 添加签名时间属性
    signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new DERUTCTime(new Date()))));
    
    // 添加撤销值属性:嵌入CRL数据
    if (crls != null && !crls.isEmpty()) {
        ASN1EncodableVector crlVectors = new ASN1EncodableVector();
        for (X509CRL crl : crls) {
            crlVectors.add(CRL.getInstance(ASN1Primitive.fromByteArray(crl.getEncoded())));
        }
        // 若有OCSP响应,将第二个参数替换为OCSPResp的ASN1结构
        RevocationValues revValues = new RevocationValues(crlVectors, null);
        signedAttrs.add(new Attribute(PKCSObjectIdentifiers.id_aa_ets_revocationValues, new DERSet(revValues)));
    }
    
    // 设置已签名属性生成器,移除directSignature模式(该模式会跳过已签名属性生成)
    signerBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeGenerator(new AttributeTable(signedAttrs)));
    
    // 用户证书处理
    X509CertificateHolder userCertHolder = new X509CertificateHolder(
            ASN1Primitive.fromByteArray(signatureModel.getChain().get(0).getEncoded()));
    signGenerator.addSignerInfoGenerator(signerBuilder.build(nonSigner, userCertHolder));
    
    return signGenerator;
}

调用修改后的生成器

// 从CA获取当前签名证书链对应的CRL列表(需自行实现获取逻辑)
List<X509CRL> crls = fetchCRLsForCertificateChain(signatureModel.getChain());
CMSSignedDataGenerator signGenerator = getCmsSignedDataGenerator(signatureModel, crls);
CMSSignedData cmsSignedData = signGenerator.generate(new CMSProcessableByteArray(signatureModel.getSignature()), false);

3. 关键注意事项

  • 必须移除原代码中的signerBuilder.setDirectSignature(true);,该模式会跳过已签名属性的自动生成,而PAdES-LT依赖已签名属性携带撤销信息和验证必要字段。
  • 已签名属性必须包含messageDigest和signingTime,否则签名无法通过标准验证。
  • 若使用OCSP响应替代CRL,需将OCSPResp转换为ASN1结构后传入RevocationValues的第二个参数。

内容的提问来源于stack exchange,提问作者Rajath R Joshi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 05:29:52