前言
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));// 将