![](https://i-blog.csdnimg.cn/direct/fb0ad49cb99c4f78b1be843d5625989e.png)
一、SM2 简介
1.1 概述
SM2
算法是基于 ECC(Elliptic Curve Cryptography)椭圆曲线密码
的 非对称加密 算法,其密钥长度为 256bit。该算法由 国家密码管理局 于 2010年12月17号发布
国密算法
,即 国家商用密码算法
。是由 国家密码管理局 认定和公布的密码算法标准及其应用规范,其中部分密码算法已经称为国际标准。如:SM系列
密码,SM 代表 商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。
国密算法包括:SM1(SCB2)、SM2、SM3、SM4、SM7、SM9,以及 ZUC(祖冲之密码)等。
其中:
- 1) SM1、SM4、SM7、ZUC(祖冲之密码)属于 对称算法;
- 2) SM2、SM9 属于 非对称算法。
- 3) SM3 属于 杂凑算法。
![](https://i-blog.csdnimg.cn/direct/f01db39a68ad43fe90f5fd1950b2b07f.png)
1.2 国密与国际密的对应关系
加密方式 | 国密 | 国际密 |
---|---|---|
对称加密 | SM1 | AES(Advanced Encryption Standard) |
非对称加密 | SM2 | RSA(Ron Rivest、Adi Shamir、Leonard Adleman)三人姓氏首字母拼在一起 |
摘要算法(杂凑) | SM3 | MD5(Message-Digest Algorithm) SHA系列(Secure Hash Algorithm) |
对称加密 | SM4 | DES(Data Encryption Standard) |
1.3 优势
SM2 算法作为一种自主创新的密码算法,具有以下优势:
- 安全性高: 基于 椭圆曲线离散对数难题,安全性比较高,能够有效地防止黑客攻击。
- 效率高: 具有较高地运算效率,能够满足大量数据加密、解密和数字签名的需求。
- 灵活性好: 支持多种密钥长度,可根据实际需求灵活选择密钥长度,适用于不同的应用场景。
- 自主创新: SM2算法是我国自主创新的密码算法,具有独立的只是产权,能够保障国家关键信息系统的信息安全。
1.4 ECC加密算法 vs RSA加密算法
SM2算法
是基于 ECC 椭圆曲线算法
实现的,采用 256位 密钥长度,它的安全强度相对较高,在工程应用中难以实现,破译或求解难度基本上是指数级的。因此,SM2 算法可以用较少的计算能力提供比 RSA算法
更高的安全强度,而所需的密钥长度却远比 RSA算法低。
对比项目 | ECC加密算法 | RSA加密算法 |
---|---|---|
密钥长度 | 246位 | 2048位 |
CPU占用 | 较少 | 较高 |
内存占用 | 较少 | 较高 |
网络小号 | 较低 | 较高 |
加密效率 | 较高 | 一般 |
破解难度 | 具有数据特性,破解难度大 | 相对ECC理论上容易些 |
抗攻击性 | 强 | 一般 |
可扩展性 | 强 | 一般 |
兼容范围 | 支持新版浏览器和操作系统,单存在少数不支持平台,例如:cPanel | 广泛支持 |
二、SM2 应用场景
2.1 数据加密
- 在非对称加密算法中,可对外公布的密钥称为
公钥
,只有持有者所知的密钥称为私钥
。发送者使用接收者的公钥来加密信息,接收者用自己的私钥解密和读取该信息。
使用 SM2 非对称加密算法加解密数据的过程:
![](https://i-blog.csdnimg.cn/direct/baa4556e2e374f319eb7864b06e6ebd2.png)
2.2 密钥协商
利用 SM2 算法进行密钥协商的过程:
-
1) 会话双方生成自己的私钥(随机数);
-
2) 会话双方由私钥、ECC椭圆曲线参数
G
各自计算出公钥; -
3) 会话双方将自己的公钥传递给对方,传递过程公开;
(由于椭圆曲线的计算复杂性高,破解难度大,因此攻击者难以通过公钥和椭圆曲线参数
G
反推出私钥) -
4) 双方将自己的 私钥 与对方的 公钥 进行运算,最终得到相同的会话密钥,该会话密钥可作为共享密钥用于对称加密(例如:
SM4算法
)通信。
![](https://i-blog.csdnimg.cn/direct/0c9aa85bd8414622ad38508e5e60d12e.png)
2.3 数字签名
数字签名
是一种用于 验证信息完整性、真实性和来源的技术手段。它通常用于确保数据在传输或存储过程中没有被篡改,并且可以追溯到特定的发送方。
- 发送方使用
自己的私钥
对信息进行 加密,生成数字签名。 - 接收方使用
发送方的公钥
对签名进行 解密 和 验证,以验证消息的完整性和真实性。
三、Java 实现 SM2 的两种方式
3.1 Maven 依赖
在 bouncycastle
- 1.57
版本之后,加入了对我国的 SM2、SM3、SM4算法的支持。
<!-- SM2加密 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
3.2 实现方式一
1)SM2Utils.java
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
/**
* 国密SM2算法工具类
**/
@Slf4j
public class SM2Utils {
private static final Charset CHARSETS = StandardCharsets.UTF_8;
private static final SM2Engine.Mode DIGEST = SM2Engine.Mode.C1C3C2;
/**
* 私钥转换为 {@link ECPrivateKeyParameters}
*
* @param key key
* @return
* @throws InvalidKeyException
*/
public static ECPrivateKeyParameters privateKeyToParams(byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
if (key == null) {
throw new RuntimeException("key must be not null !");
}
PrivateKey privateKey = generatePrivateKey(key);
return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
}
/**
* 生成私钥
*
* @param key key
* @return
*/
public static PrivateKey generatePrivateKey(byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
if (key == null) {
throw new RuntimeException("key must be not null !");
}
KeySpec keySpec = new PKCS8EncodedKeySpec(key);
return getKeyFactory().generatePrivate(keySpec);
}
/**
* 公钥转换为 {@link ECPublicKeyParameters}
*
* @param key key
* @return
* @throws InvalidKeyException
*/
public static ECPublicKeyParameters publicKeyToParams(byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
if (key == null) {
throw new RuntimeException("key must be not null !");
}
// 生成公钥
KeySpec keySpec = new X509EncodedKeySpec(key);
PublicKey publicKey = getKeyFactory().generatePublic(keySpec);
return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
}
/**
* 获取{@link KeyFactory}
*
* @return {@link KeyFactory}
*/
private static KeyFactory getKeyFactory() throws NoSuchAlgorithmException {
final Provider provider = new BouncyCastleProvider();
return KeyFactory.getInstance("EC", provider);
}
/**
* 加密
*
* @param dataStr 数据
* @param publicKey 公钥
* @return 加密之后的数据
*/
public static String encrypt(String dataStr, String publicKey) {
try {
return Base64.toBase64String(encrypt(dataStr.getBytes(StandardCharsets.UTF_8), Base64.decode(publicKey)));
} catch (Exception e) {
throw new RuntimeException("参数加密异常");
}
}
public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams(publicKey));
SM2Engine engine = new SM2Engine(DIGEST);
engine.init(true, pubKeyParameters);
return engine.processBlock(data, 0, data.length);
}
/**
* 解密
*
* @param base64Data 数据
* @param base64PrivateKey 私钥
* @return 解密之后的数据
*/
public static String decrypt(String base64Data, String base64PrivateKey) throws Exception {
byte[] data = Base64.decode(base64Data);
byte[] privateKey = Base64.decode(base64PrivateKey);
return new String(decrypt(data, privateKey), CHARSETS);
}
public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
CipherParameters privateKeyParameters = privateKeyToParams(privateKey);
SM2Engine engine = new SM2Engine(DIGEST);
engine.init(false, privateKeyParameters);
return engine.processBlock(data, 0, data.length);
}
/**
* 签名
*
* @param data 数据
* @return 签名
*/
public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = new ParametersWithRandom(privateKeyToParams(privateKey));
signer.init(true, param);
signer.update(data, 0, data.length);
return signer.generateSignature();
}
/**
* 用公钥检验数字签名的合法性
*
* @param data 数据
* @param sign 签名
* @param publicKey 公钥
* @return 是否验证通过
*/
public static boolean verify(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = publicKeyToParams