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

如何结合SM2与SM4实现符合PKCS7规范的信封加密?

解决基于PKCS7的SM2+SM4信封加密问题(BouncyCastle实现)

我刚碰到过类似的国密CMS信封加密坑,你的核心痛点是BouncyCastle对SM4的CMS支持需要特定配置,还要把SM2密钥加密和SM4内容加密的逻辑正确整合。下面一步步帮你解决:

一、先搞定环境依赖

首先确保你用的是BouncyCastle 1.68及以上版本(1.68开始对国密CMS的支持才比较完善),Maven依赖可以这么加:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

必须同时引入bcprov(核心算法实现)和bcpkix(CMS/PKCS7相关工具类),否则会缺关键类。

二、解决"No such algorithm"异常

你用GMObjectIdentifier.sms4_cbc报错,本质是默认的JceCMSContentEncryptorBuilder没把这个OID映射到BC的SM4实现。解决办法很简单:

  1. 提前注册BouncyCastle安全提供者
  2. 构建加密器时,明确指定BC提供者,强制让框架用BC的SM4实现

三、完整整合代码

下面把你两段代码的逻辑完全整合,实现PKCS7信封加密(SM4加密内容,SM2加密SM4密钥):

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import java.security.Security;
import java.security.cert.X509Certificate;
import java.nio.charset.StandardCharsets;

public class SM2SM4EnvelopeEncryptor {

    // 提前注册BC提供者,建议在应用启动时执行一次
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 基于PKCS7的SM2+SM4信封加密
     * @param message 待加密明文
     * @param cert SM2签名证书(公钥用于加密SM4密钥)
     * @return Base64编码的PKCS7信封密文
     * @throws Exception 加密过程异常
     */
    public static String encryptMessage(String message, X509Certificate cert) throws Exception {
        CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator();

        // 1. 添加SM2密钥传输的收件人信息生成器
        // 因为你的证书是SM3WITHSM2签名算法,必须指定SM2的密钥加密OID
        JceKeyTransRecipientInfoGenerator recipientGen = new JceKeyTransRecipientInfoGenerator(
                new X509CertificateHolder(cert.getEncoded())
        ).setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .setAlgorithm(GMObjectIdentifiers.sm2encrypt);

        gen.addRecipientInfoGenerator(recipientGen);

        // 2. 构建SM4-CBC内容加密器
        // 关键:用GMObjectIdentifier.sms4_cbc指定算法,同时绑定BC提供者
        var encryptor = new JceCMSContentEncryptorBuilder(GMObjectIdentifiers.sms4_cbc)
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .build();

        // 3. 封装待加密内容
        CMSTypedData content = new CMSProcessableByteArray(
                message.getBytes(StandardCharsets.UTF_8)
        );

        // 4. 生成PKCS7信封密文
        CMSEnvelopedData envelopedData = gen.generate(content, encryptor);

        // 5. Base64编码返回
        return Base64.toBase64String(envelopedData.getEncoded());
    }

    // 复用你原来的SM2公钥提取逻辑(可选,上面的加密方法已直接用证书)
    public static org.bouncycastle.crypto.params.ECPublicKeyParameters extractSM2PublicKey(X509Certificate cert) {
        if (cert.getPublicKey() instanceof org.bouncycastle.jce.interfaces.BCECPublicKey) {
            org.bouncycastle.jce.interfaces.BCECPublicKey bcEcPublicKey =
                    (org.bouncycastle.jce.interfaces.BCECPublicKey) cert.getPublicKey();
            org.bouncycastle.jce.spec.ECParameterSpec ecParameterSpec = bcEcPublicKey.getParameters();
            org.bouncycastle.crypto.params.ECDomainParameters ecDomainParams = new org.bouncycastle.crypto.params.ECDomainParameters(
                    ecParameterSpec.getCurve(),
                    ecParameterSpec.getG(),
                    ecParameterSpec.getN()
            );
            return new org.bouncycastle.crypto.params.ECPublicKeyParameters(
                    bcEcPublicKey.getQ(),
                    ecDomainParams
            );
        }
        throw new IllegalArgumentException("Not a SM2 ECPublicKey");
    }
}

四、关键细节解释

  1. 注册BC提供者:必须在代码开头或应用启动时执行,否则JVM不会加载BouncyCastle的国密算法实现。
  2. SM2密钥加密指定:因为你的证书是SM3WITHSM2类型,密钥加密必须用GMObjectIdentifiers.sm2encrypt,不能用默认的RSA算法,否则会和证书公钥类型不匹配。
  3. SM4加密器构建:直接用GMObjectIdentifiers.sms4_cbc作为算法参数,同时绑定BC提供者,就能让框架找到对应的SM4实现,解决"No such algorithm"问题。
  4. 证书处理:用X509CertificateHolder包装证书,是BouncyCastle CMS工具类的标准用法,能更好兼容国密证书格式。

五、测试示例

public static void main(String[] args) throws Exception {
    // 从文件加载SM2证书(替换成你的证书路径)
    java.io.FileInputStream fis = new java.io.FileInputStream("sm2-cert.cer");
    java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);

    // 测试加密
    String plainText = "这是测试明文";
    String encrypted = encryptMessage(plainText, cert);
    System.out.println("PKCS7信封密文:" + encrypted);
}

常见问题排查

  • 如果还是报"No such algorithm":检查BouncyCastle版本是否≥1.68,是否同时引入了bcprov和bcpkix,是否注册了BC提供者。
  • 如果报公钥类型不匹配:确认证书公钥是SM2的(可以通过cert.getPublicKey().getAlgorithm()查看,应该是"EC"或"SM2")。
  • 如果加密后解密失败:确保解密端用相同版本的BouncyCastle,并且用SM2私钥解密SM4密钥,再用SM4解密内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:19:41