如何结合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实现。解决办法很简单:
- 提前注册BouncyCastle安全提供者
- 构建加密器时,明确指定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"); } }
四、关键细节解释
- 注册BC提供者:必须在代码开头或应用启动时执行,否则JVM不会加载BouncyCastle的国密算法实现。
- SM2密钥加密指定:因为你的证书是SM3WITHSM2类型,密钥加密必须用
GMObjectIdentifiers.sm2encrypt,不能用默认的RSA算法,否则会和证书公钥类型不匹配。 - SM4加密器构建:直接用
GMObjectIdentifiers.sms4_cbc作为算法参数,同时绑定BC提供者,就能让框架找到对应的SM4实现,解决"No such algorithm"问题。 - 证书处理:用
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
相关产品推荐
相关产品推荐

