SM2国密算法
SM2国密算法
SM2是中华人民共和国政府采用的一种公开密钥加密标准,由国家密码管理局于2010年12月17日发布,相关标准为“GM/T 0003-2012 《SM2椭圆曲线公钥密码算法》”。2016年,成为中国国家密码标准(GB/T 32918-2016)。
SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。
随着密码技术和计算机技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。
SM2和RSA对比
SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小。
X.509公钥、PKCS#8私钥 与 裸公钥、裸私钥的区别
裸公钥与裸私钥
使用场景:
• 协议内部直接传输(如密钥交换)。
• 不包含算法和曲线信息,仅适合算法已知的上下文中使用。
优点:
• 简单高效,无额外开销。
缺点:
• 无法直接应用于需要算法标识的场景(如证书或加密工具)。
X.509 格式公钥
定义:
• 一种标准化的公钥封装格式,封装了裸公钥及其相关元数据(如算法标识)。
• 广泛用于数字证书、公钥基础设施(PKI)中。
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier, -- 算法标识
subjectPublicKey BIT STRING -- 裸公钥内容
}
• algorithm:指定算法和参数,例如: SM2 的算法标识 OID 为 1.2.156.10197.1.301。
• subjectPublicKey:封装的裸公钥数据,通常为未压缩格式。
编码形式:
• DER 格式:ASN.1 定义的二进制编码。
• PEM 格式:Base64 编码的 DER 数据,带有 -----BEGIN PUBLIC KEY----- 标签。
使用场景:
• 广泛应用于数字证书、SSL/TLS 等场景,需要明确公钥算法和曲线参数。
优点:
• 标准化,便于与 PKI 系统和加密工具集成。
• 包含算法信息,可用于多种上下文。
缺点:
• 相较裸公钥稍大,封装开销略高。
PKCS#8 格式私钥
定义:
• 一种标准化的私钥封装格式,封装了裸私钥及其元数据(如算法标识)。
• 常用于私钥文件存储和传输。
结构:
• 使用 ASN.1 格式定义的 PrivateKeyInfo 结构:
PrivateKeyInfo ::= SEQUENCE {
version Version, -- 版本号(通常为 0)
privateKeyAlgorithm AlgorithmIdentifier, -- 算法标识
privateKey OCTET STRING, -- 裸私钥内容
attributes [0] IMPLICIT Attributes OPTIONAL -- 可选属性
}
• privateKeyAlgorithm:指定私钥使用的算法,例如:SM2 的算法标识 OID 为 1.2.156.10197.1.301。
• privateKey:封装的裸私钥数据,通常为 32 字节。
编码形式:
• DER 格式:ASN.1 定义的二进制编码。
• PEM 格式:Base64 编码的 DER 数据,带有 -----BEGIN PRIVATE KEY----- 标签。
使用场景:
• 私钥存储文件(如 .pem 文件)。
• 适用于标准化工具和 API 的场景。
优点:
• 标准化,适用于多种加密库和工具。
• 包含算法信息,便于跨平台使用。
缺点:
• 相较裸私钥稍大,封装开销略高。
选择建议
• 裸公钥/裸私钥:效率优先,适合内部使用或上下文明确的场景。
• X.509 公钥/PKCS#8 私钥:标准化应用场景,适合证书、文件存储和跨平台使用。
Java代码
package com.autumn.util;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* SM2国密算法
*
* 密钥钥编码形式:
* 原始公钥 -》 原始私钥
* PEM文件格式: 公钥X.509 -》 私钥PKCS#8
* DER文件格式: 私钥按照 ASN.1 规则进行二进制编码,通常结合 PKCS#8 使用
*
* PKCS#7 常用的后缀是: .P7B .P7C .SPC
* PKCS#12 常用的后缀有: .P12 .PFX
* X.509 DER 二进制编码(ASCII)的后缀是: .DER .CER .CRT
* X.509 PEM Base64编码(Base64)的后缀是: .PEM .CER .CRT
* .cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
*/
public class Sm2EncryptionUtil {
//base64裸密钥
private static final String PUBLIC_KEY = "BJ0GzQWxQK2e2KjFBO/CqZ5/JNwhJge5szW3wxLoiwgKpqF6m/G6sjbuA4syR1fey3yIfnmw9Tmm6344UzyChk4=";
//base64裸私钥
private static final String PRIVATE_KEY = "Fd314wICBHdG5MwFFPzYNUb/DUppV6vIYj7hmpLqZPk=";
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final X9ECParameters EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1");
private static final ECDomainParameters DOMAIN_PARAMETERS = new ECDomainParameters(
EC_PARAMETERS.getCurve(),
EC_PARAMETERS.getG(),
EC_PARAMETERS.getN(),
EC_PARAMETERS.getH()
);
/**
* SM2 加密
*
* @param data 原始数据
* @param publicKeyString 公钥的base64字符串
* @return 加密后的字符串
* @throws Exception 加密异常
*/
public static String encrypt(String data, String publicKeyString) throws Exception {
if (publicKeyString == null || publicKeyString.isEmpty()) {
throw new IllegalArgumentException("Public key string cannot be null or empty");
}
// 解码base64字符串公钥
byte[] publicKeyBytes = Base64.decode(publicKeyString);
// 提取 ECPoint
ECPoint q = EC_PARAMETERS.getCurve().decodePoint(publicKeyBytes);
if (q == null) {
throw new IllegalArgumentException("Failed to decode public key point");
}
// 设置公钥参数
ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(q, DOMAIN_PARAMETERS);
// 加密
SM2Engine engine = new SM2Engine();
engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
byte[] encryptedData = engine.processBlock(data.getBytes(), 0, data.getBytes().length);
// 返回base64加密后的字符串
return Base64.toBase64String(encryptedData);
}
/**
* SM2 解密
*
* @param encryptedData 加密数据
* @param privateKeyString 私钥的base64字符串
* @return 解密后的字符串
* @throws Exception 解密异常
*/
public static String decrypt(String encryptedData, String privateKeyString) throws Exception {
if (privateKeyString == null || privateKeyString.isEmpty()) {
throw new IllegalArgumentException("Private key string cannot be null or empty");
}
// 解码私钥
byte[] privateKeyBytes = Base64.decode(privateKeyString);
BigInteger d = new BigInteger(1, privateKeyBytes);
// 设置私钥参数
ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, DOMAIN_PARAMETERS);
// 解密
SM2Engine engine = new SM2Engine();
engine.init(false, privateKeyParams);
// 先base64解密,再用sm2解密
byte[] decryptedData = engine.processBlock(Base64.decode(encryptedData), 0, Base64.decode(encryptedData).length);
return new String(decryptedData);
}
// 从 X.509 格式的公钥中提取 ECPoint
public static ECPoint getEcPointFromX509PublicKey(String publicKeyBase64) throws Exception {
// 解码 BASE64 字符串
byte[] publicKeyBytes = Base64.decode(publicKeyBase64);
// 使用 KeyFactory 转换为 PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// 将 PublicKey 转换为 ECPublicKeySpec
ECPublicKeySpec ecPublicKeySpec = (ECPublicKeySpec) keyFactory.getKeySpec(publicKey, ECPublicKeySpec.class);
// 返回 ECPoint
return ecPublicKeySpec.getQ();
}
/**
* SM2 X509加密
* @param data 原始数据
* @param publicKeyString 公钥的base64字符串
* @return 加密后的字符串
* @throws Exception 加密异常
*/
public static String encryptX509(String data, String publicKeyString) throws Exception {
if (publicKeyString == null || publicKeyString.isEmpty()) {
throw new IllegalArgumentException("Public key string cannot be null or empty");
}
// 原始的公钥使用base64字符串
// byte[] publicKeyBytes = Base64.decode(publicKeyString);
// X509公钥使用
ECPoint q = getEcPointFromX509PublicKey(publicKeyString);
if (q == null) {
throw new IllegalArgumentException("Failed to decode public key point");
}
// 设置公钥参数
ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(q, DOMAIN_PARAMETERS);
// 加密
SM2Engine engine = new SM2Engine();
engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
byte[] encryptedData = engine.processBlock(data.getBytes(), 0, data.getBytes().length);
// 返回base64加密后的字符串
return Base64.toBase64String(encryptedData);
}
//私钥是 PKCS#8 格式
public static BigInteger getPrivateKeyScalar(String privateKeyBase64) throws Exception {
// 解码 Base64 字符串
byte[] privateKeyBytes = Base64.decode(privateKeyBase64);
// 使用 KeyFactory 将其解析为 PrivateKey 对象
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 提取 ECPrivateKeySpec 并返回标量值
ECPrivateKeySpec ecPrivateKeySpec = (ECPrivateKeySpec) keyFactory.getKeySpec(privateKey, ECPrivateKeySpec.class);
return ecPrivateKeySpec.getD();
}
/**
* SM2 使用PKCS#8 解密
* @param encryptedData
* @param privateKeyString
* @return
* @throws Exception
*/
public static String decryptPKCS8(String encryptedData, String privateKeyString) throws Exception {
if (privateKeyString == null || privateKeyString.isEmpty()) {
throw new IllegalArgumentException("Private key string cannot be null or empty");
}
// 获取标量值
BigInteger d = getPrivateKeyScalar(privateKeyString);
// 验证范围
if (d.compareTo(BigInteger.ONE) < 0 || d.compareTo(DOMAIN_PARAMETERS.getN().subtract(BigInteger.ONE)) >= 0) {
throw new IllegalArgumentException("Private key is out of range [1, n-1]");
}
// 设置私钥参数
ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, DOMAIN_PARAMETERS);
// 解密
SM2Engine engine = new SM2Engine();
engine.init(false, privateKeyParams);
byte[] decryptedData = engine.processBlock(Base64.decode(encryptedData), 0, Base64.decode(encryptedData).length);
return new String(decryptedData);
}
/**
* 生成 SM2 密钥对 裸公钥 裸私钥
* @return Map<String, String>
* @throws Exception
*/
public static Map<String, String> generateSm2KeyPair() throws Exception {
// X9ECParameters ecParameters = org.bouncycastle.asn1.sec.SECNamedCurves.getByName("sm2p256v1");
ECNamedCurveParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("sm2p256v1");
if (ecParameters == null) {
System.out.println("sm2p256v1 curve not found.");
return null;
}
ECDomainParameters domainParameters = new ECDomainParameters(
ecParameters.getCurve(),
ecParameters.getG(),
ecParameters.getN(),
ecParameters.getH());
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));
AsymmetricCipherKeyPair keyPair = keyGen.generateKeyPair();
ECPrivateKeyParameters privateKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKeyParams = (ECPublicKeyParameters) keyPair.getPublic();
String publicKey = Base64.toBase64String(publicKeyParams.getQ().getEncoded(false));
String privateKey = Base64.toBase64String(privateKeyParams.getD().toByteArray());
Map<String, String> keyMap = new HashMap<>();
keyMap.put("publicKey", publicKey);
keyMap.put("privateKey", privateKey);
return keyMap;
}
/**
* 生成 SM2 密钥对,并输出 X.509 公钥和 PKCS#8 私钥
*
* @return Map<String, String> 包含 X.509 格式的公钥和 PKCS#8 格式的私钥
* @throws Exception
*/
public static Map<String, String> generateX509Sm2KeyPair() throws Exception {
// 获取 SM2 曲线参数
ECNamedCurveParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("sm2p256v1");
if (ecParameters == null) {
throw new IllegalArgumentException("sm2p256v1 curve not found.");
}
ECDomainParameters domainParameters = new ECDomainParameters(
ecParameters.getCurve(),
ecParameters.getG(),
ecParameters.getN(),
ecParameters.getH());
// 初始化密钥对生成器
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));
AsymmetricCipherKeyPair keyPair = keyGen.generateKeyPair();
// 获取私钥和公钥参数
ECPrivateKeyParameters privateKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKeyParams = (ECPublicKeyParameters) keyPair.getPublic();
// 转换公钥为 X.509 格式
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKeyParams);
String x509PublicKey =
Base64.toBase64String(publicKeyInfo.getEncoded());
// 转换私钥为 PKCS#8 格式
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParams);
String pkcs8PrivateKey =
Base64.toBase64String(privateKeyInfo.getEncoded());
// 返回结果
Map<String, String> keyMap = new HashMap<>();
keyMap.put("publicKey", x509PublicKey);
keyMap.put("privateKey", pkcs8PrivateKey);
return keyMap;
}
public static void main(String[] args) throws Exception {
//1.生成裸公钥 裸私钥
Map<String, String> keyPair = generateSm2KeyPair();
System.out.println("Generated Original Public Key: " + keyPair.get("publicKey"));
System.out.println("Generated Original Private Key: " + keyPair.get("privateKey"));
String data = "Hello, SM2!";
String encryptedData = encrypt(data, keyPair.get("publicKey"));
String decryptedData = decrypt(encryptedData, keyPair.get("privateKey"));
System.out.println("Original Data: " + data);
System.out.println("Encrypted Data: " + encryptedData);
System.out.println("Decrypted Data: " + decryptedData);
//2.生成X.509公钥和PKCS#8私钥
Map<String, String> sm2KeyPair = generateX509Sm2KeyPair();
System.out.println("X.509 公钥:\n" + "-----BEGIN PUBLIC KEY-----\n" +sm2KeyPair.get("publicKey")+"\n-----END PUBLIC KEY-----");
System.out.println("PKCS#8 私钥:\n" +"-----BEGIN PRIVATE KEY-----\n" + sm2KeyPair.get("privateKey")+"\n-----END PRIVATE KEY-----");
String encryptedData2 = encryptX509(data, sm2KeyPair.get("publicKey"));
String decryptedData2 = decryptPKCS8(encryptedData2, sm2KeyPair.get("privateKey"));
System.out.println("Original Data: " + data);
System.out.println("X.509 公钥 Encrypted Data: " + encryptedData2);
System.out.println("PKCS#8 私钥 Decrypted Data: " + decryptedData2);
}
}