前言
SM2是中国国家密码管理局发布的椭圆曲线公钥密码算法标准(GB/T 32918),属于国密算法体系。与RSA和ECDSA相比,SM2在相同安全强度下密钥更短、计算效率更高。本文将介绍如何在Java中实现SM2的密钥生成、数字签名、验签、加密及解密功能。
一、结果验证
1.代码运行结果
1.1 不带id签名验签代码运行结果
1.2 带id签名验签代码运行结果
1.3 SM2加密解密代码运行结果
2.工具验证结果
2.1 不带id签名验签工具运行结果
2.2 带id签名验签工具运行结果
2.3 SM2加密解密工具运行结果
二、SM2签名原理
SM2签名过程的核心是利用私钥对消息进行签名,生成签名值 (r, s)
。具体步骤如下:
计算消息的哈希值
使用SM3哈希算法对消息M
进行哈希处理,得到哈希值e
。生成随机数
选择一个随机数k
,满足1 < k < n
,其中n
是椭圆曲线的阶。计算椭圆曲线点
使用随机数k
计算椭圆曲线上的点Q = kG
,其中G
是椭圆曲线的基点。取点Q
的x
坐标x1
。计算签名值
r
计算r = (e + x1) mod n
。如果r = 0
或r + k = n
,则重新选择随机数k
。计算签名值
s
计算s = (1 + d)^{-1} * (k - r * d) mod n
,其中d
是私钥。输出签名结果
签名结果为(r, s)
,通常以字节数组的形式存储和传输。
三、SM2验签原理
SM2验签过程的核心是利用公钥验证签名的有效性。具体步骤如下:
计算消息的哈希值
使用SM3哈希算法对消息M
进行哈希处理,得到哈希值e
。计算值
t
计算t = (r + s) mod n
,其中r
和s
是签名值。计算椭圆曲线点
计算点R = sG + tP
,其中G
是椭圆曲线的基点,P
是签名者的公钥。取点R
的x
坐标x1
。验证签名
验证等式r = (e + x1) mod n
是否成立。如果成立,则签名有效;否则,签名无效。
四、SM2签名与验签的Java实现
1. 添加依赖
在pom.xml
中添加Bouncy Castle依赖:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
2. 生成密钥对
/**
* 生成SM2密钥对。
*
* @return 生成的密钥对(包含公钥和私钥)
* @throws Exception 如果密钥生成过程中发生错误
*/
public static KeyPair generateKeyPair() throws Exception {
// 添加Bouncy Castle安全提供者
Security.addProvider(new BouncyCastleProvider());
// 获取SM2椭圆曲线参数(使用sm2p256v1曲线)
ECParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
// 创建EC密钥对生成器实例
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
// 初始化密钥对生成器,指定椭圆曲线参数和随机数生成器
kpg.initialize(sm2Spec, new SecureRandom());
// 生成密钥对并返回
return kpg.generateKeyPair();
}
3. 签名不带ID
/**
* 使用SM2算法进行签名(不使用用户ID)。
*
* @param data 待签名的数据(字节数组)
* @param privateKey 签名使用的私钥
* @return 签名结果(字节数组)
* @throws Exception 如果签名过程中发生错误
*/
public static String signNoId(byte[] data, PrivateKey privateKey) throws Exception {
// 创建SM2签名实例,指定使用SM3哈希算法
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
// 初始化签名器,使用私钥
signature.initSign(privateKey);
// 更新待签名的数据
signature.update(data);
// 生成签名
byte[] signatureBytes = signature.sign();
// 解析 DER 编码的签名结果
ASN1Sequence sequence = ASN1Sequence.getInstance(signatureBytes);
BigInteger r = ASN1Integer.getInstance(sequence.getObjectAt(0)).getValue();
BigInteger s = ASN1Integer.getInstance(sequence.getObjectAt(1)).getValue();
// 打印 r 和 s 的值
System.out.println("r 的十六进制值: " + r.toString(16));
System.out.println("s 的十六进制值: " + s.toString(16));
// 将