一、简介
SM2国密算法是国产椭圆曲线非对称公钥算法,国家商用密码标准,2010 年国密局发布,替代 RSA,分三大能力:数字签名、公钥加解密、密钥协商,必须搭配国密哈希 SM3 使用。
标准使用GM/T 0003-2012,国标 GB/T 32918-2016,已纳入 ISO 国际密码标准。椭圆曲线离散对数 ECDLP 难题,固定国密标准素域曲线sm2p256v1(CURVE_SM2),曲线参数由国家密码管理局固定,不可自定义。256 位 SM2 ≈ RSA3072 安全强度,远优于 RSA2048;密钥更短、运算更快、CPU 开销低。

二、主要功能模块
1.数字签名算法
用于身份认证和数据完整性验证基于椭圆曲线离散对数问题(ECDLP)签名长度:64字节(r和s各32字节)
javascript
签名流程:
预处理:用用户 ID + 公钥 + SM3生成 Z 值,H=SM3(Z||原文M)(区别于 ECDSA:带用户 ID 防公钥替换攻击)
随机生成 k,计算k*G=(x1,y1)(G 为国密曲线基点)
r=(H+x1) mod n、s=(k-r.d).(k^{-1})mod n,输出Sign(r,s)
验签流程:
公钥 Q=d*G,还原 r、s;通过公钥反向计算校验 r 是否合法,一致则验签通过
业务场景:接口签名、电子签章、证书签名、区块链签名
2.密钥交换协议
双方通过协商生成共享会话密钥
支持双向身份认证
防止中间人攻击
3.公钥加密算法
加密:接收方公钥加密明文 M → C1+C2+C3
解密:接收方私钥解密 C1,派生密钥解密 C2,校验 C3 哈希防篡改
限制:非对称只能加密短数据(小于曲线阶长≈32 字节),长数据必须 SM2 加密 SM4 密钥、SM4 加密明文(混合加密)
三、核心身份与说明
1.核心身份
标准 :GM/T 0003、GB/T 32918
类型 :非对称加密(公钥 + 私钥)
哈希 :必须搭配 SM3
安全 :SM2 256 位 ≈ RSA 3072
地位:国密必用、金融 / 政务 / 等保强制标准
2.核心说明
密钥格式
私钥:32 字节
公钥:64 字节(非压缩)/33 字节(压缩)
签名规则
算法:SM3withSM2
签名长度:64 字节
加密规则
密文结构:C1 (65 字节) + C3 (32 字节) + C2 (密文)
限制:只能加密 < 32 字节数据,长数据用 SM2 加密 SM4 密钥 + SM4 加密数据
四、SM2 vs RSA / 国际 ECC (ECDSA) 区别
1.对比 RSA
RSA:大数分解难题,密钥长、加解密慢、量子安全性差;SM2 密钥短、性能高、抗量子更强、国产合规
2.对比 ECDSA (国际 ECC)
ECDSA:曲线自由定义、无用户 ID;SM2 固定国密曲线、签名带用户 ID 预处理、强制 SM3 哈希、KDF 密钥派生规则国标化,安全性更强、规范统一
五、应用场景
金融:网银签名、支付验签、电子票据
政务:国密 SSL 证书、电子签章、公文签名
物联网 / 嵌入式:智能卡、设备密钥(小密钥省存储)
系统改造:等保合规项目,替换原有 RSA 证书、接口验签
六、代码实战
1.引入依赖
java
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.5</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.5</version>
</dependency>
2.SM2Util工具类
java
public class SM2Util {
// 注册BouncyCastle提供者
static {
Security.addProvider(new BouncyCastleProvider());
}
// SM2标准曲线参数 sm2p256v1
private static final X9ECParameters SM2_EC_PARAMS = GMNamedCurves.getByName("sm2p256v1");
private static final ECDomainParameters SM2_DOMAIN_PARAMS = new ECDomainParameters(
SM2_EC_PARAMS.getCurve(),
SM2_EC_PARAMS.getG(),
SM2_EC_PARAMS.getN(),
SM2_EC_PARAMS.getH()
);
// 1. 生成 SM2 密钥对(公钥+私钥)
public static AsymmetricCipherKeyPair generateKeyPair() {
ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(
SM2_DOMAIN_PARAMS, new SecureRandom()
);
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyGenParams);
return generator.generateKeyPair();
}
// 2. SM2 签名(私钥签名)
public static byte[] sign(byte[] privateKeyBytes, byte[] data) throws Exception {
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateKeyBytes);
SM2Signer signer = new SM2Signer();
signer.init(true, privateKey);
signer.update(data, 0, data.length);
return signer.generateSignature();
}
// 3. SM2 验签(公钥验签)
public static boolean verify(byte[] publicKeyBytes, byte[] data, byte[] signature) throws Exception {
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) PublicKeyFactory.createKey(publicKeyBytes);
SM2Signer signer = new SM2Signer();
signer.init(false, publicKey);
signer.update(data, 0, data.length);
return signer.verifySignature(signature);
}
// 4. SM2 加密(公钥加密)
public static byte[] encrypt(byte[] publicKeyBytes, byte[] data) throws Exception {
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) PublicKeyFactory.createKey(publicKeyBytes);
SM2Engine engine = new SM2Engine(new SM3Digest());
engine.init(true, publicKey);
return engine.processBlock(data, 0, data.length);
}
// 5. SM2 解密(私钥解密)
public static byte[] decrypt(byte[] privateKeyBytes, byte[] encryptedData) throws Exception {
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateKeyBytes);
SM2Engine engine = new SM2Engine(new SM3Digest());
engine.init(false, privateKey);
return engine.processBlock(encryptedData, 0, encryptedData.length);
}
}
3.签名证书制作
java
public static String generateSelfSignedCertificate(KeyPair keyPair, String commonName, boolean pemFormat) throws Exception {
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
X500Name issuer = new X500Name("CN=" + commonName + ",O=AniCert,C=CN");
X500Name subject = new X500Name("CN=" + commonName + ",O=AniCert,C=CN");
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.set(2026, Calendar.MAY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date notBefore = calendar.getTime();
Calendar calendar1 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar1.set(2046, Calendar.MAY, 1, 0, 0, 0);
calendar1.set(Calendar.MILLISECOND, 0);
Date notAfter = calendar1.getTime();
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
issuer,
serialNumber,
notBefore,
notAfter,
subject,
publicKeyInfo
);
//证书签名算法
ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2")
.setProvider(bcProvider)
.build(privateKey);
org.bouncycastle.cert.X509CertificateHolder certHolder = certBuilder.build(signer);
//生成证书
java.security.cert.X509Certificate certificate = new JcaX509CertificateConverter()
.setProvider(bcProvider)
.getCertificate(certHolder);
byte[] certBytes = certificate.getEncoded();
if (pemFormat) {
String base64Cert = Base64.encode(certBytes);
return "-----BEGIN CERTIFICATE-----\n" +
formatBase64(base64Cert) +
"\n-----END CERTIFICATE-----";
} else {
return Base64.encode(certBytes);
}
}
4.证书加解密验证
java
// 解密需要 32位
BCECPrivateKey privateKeyObj = SM2KeyUtil.decodeWithPKCS8(privateKey);
String privateKeyRaw = SM2KeyUtil.encodeStrWithBigInteger(privateKeyObj);
StaticLog.info("原始D值私钥:{}", privateKeyRaw);
String certDerBase64 = SM2KeyUtil.generateSelfSignedCertificate(smKeyPair, "xxxx", false);
StaticLog.info("私钥(PKCS8 Base64):{}", privateKey);
StaticLog.info("证书(DER Base64):{}", certDerBase64);
// StaticLog.info("证书(PEM格式):\n{}", certPem);
StaticLog.info("--------证书信息验证--------");
org.bouncycastle.asn1.x509.Certificate cert = CertUtils.parseSM2Certificate(certDerBase64);
String subjectDN = CertUtils.getCertInfo(cert, 21);
String issuerDN = CertUtils.getCertInfo(cert, 20);
String publicKeyFromCert = CertUtils.getCertInfo(cert, 30);
String expiryDate = CertUtils.getCertInfo(cert, 32);
StaticLog.info("证书主体:{}", subjectDN);
StaticLog.info("证书颁发者:{}", issuerDN);
StaticLog.info("证书公钥:{}", publicKeyFromCert);
StaticLog.info("证书有效期截止:{}", expiryDate);
String originalData = "我是需要加密的数据";
StaticLog.info("原始数据:{}", originalData);
ISoftCryptoService iSoftCryptoService = new SoftCryptoServiceImpl();
String encryptedData = iSoftCryptoService.encrypt(publicKeyFromCert, originalData);
StaticLog.info("加密后数据:{}", encryptedData);
String decryptedData = iSoftCryptoService.decrypt(privateKeyRaw, encryptedData);
StaticLog.info("解密后数据:{}", decryptedData);
七、总结
SM2 = 国密非对称,签名 + 加密,配 SM3,替代 RSA
SM2(非对称) :签名、密钥加密
SM3(哈希) :消息摘要(对标 SHA256)
SM4(对称 128 位) :大数据加解密(对标 AES)