SM2的签名及验签算法,主要是由SM2Signer这个类来实现的,它的构造函数很多,本文只介绍
public SM2Signer(DSAEncoding encoding);
1
这一个,也即默认的摘要算法为SM3,定制一下输出的签名结果。目标是签名后,期望得到的是一个64字节组成的二进制数组,也即国密规范文档中提到的r||s
。
注意
本文不介绍SM2具体的使用细节,原理性的内容,所以要求要对SM2算法的作用和大概用法有事先的了解。
# 输入输出处理
实现一个DSAEncoding类,使签名输出的结果一个64字节的byte数组,内容即为r||s
。验签时的输入也是一个64字节byte数组。
/**
* r||s编解码器,使得签名结果为64字节的二进制数据
* 因为大部分场景下,都是需要64字节的签名结果
*/
private class SM2RSEncoding implements DSAEncoding {
@Override
public BigInteger[] decode(BigInteger n, byte[] encoding) throws IOException {
if(encoding == null || encoding.length != 64) {
throw new IllegalArgumentException("Malformed signature");
}
byte[] buf = new byte[32];
System.arraycopy(encoding, 0, buf, 0, 32);
BigInteger r = checkValue(n, new BigInteger(1, buf));
System.arraycopy(encoding, 32, buf, 0, 32);
BigInteger s = checkValue(n, new BigInteger(1, buf));
return new BigInteger[]{r, s};
}
@Override
public byte[] encode(BigInteger n, BigInteger r, BigInteger s) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream(65);
bout.write(BigIntegers.asUnsignedByteArray(32, checkValue(n, r)));
bout.write(BigIntegers.asUnsignedByteArray(32, checkValue(n, s)));
return bout.toByteArray();
}
BigInteger checkValue(BigInteger n, BigInteger x) {
if (x.signum() < 0 || (null != n && x.compareTo(n) >= 0)) {
throw new IllegalArgumentException("Value out of range");
}
return x;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
这里的encode即是处理签名的结果输出,decode即是处理验签时的签名值输入。其中的参数n,即为SM2曲线中的参数n,无论是输出还是输入,都要校验大数是否符合SM2曲线的要求。
# 固定随机数
因为SM2每次签名时,都会随机生成一对密钥对,不方便写单元测试,因此需要实现一个固定随机数生成的类,使得每次签名生成的密钥对都一样,才好将签名结果与预期值进行比较,保证代码的正确性。
/**
* 单元测试需要,固定每次签名生成的随机K,实际生产可千万不能这么用
*/
private class FixedRandom extends SecureRandom {
byte[] fixedK = Hex.decode("b8b08eae2876ef4e24bc7b3e95373b39246cdcce58aaf6cdaf42874369ba1ff3");
@Override
public void nextBytes(byte[] bytes) {
System.arraycopy(fixedK, 0, bytes, 0, 32);
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
警告
生产时,可不能这么用哦。具体生产时注意细节,在后续代码里有备注。
# 默认UserId签名
默认的UserId为:1234567812345678
/**
* SM2签名算法,采用默认的USERID:1234567812345678
*/
@Test
public void testSignWithDefaultUserId() {
// 获取国密曲线
X9ECParameters gmParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造Domain参数
ECDomainParameters gmDomainParameters = new ECDomainParameters(gmParameters.getCurve(),
gmParameters.getG(), gmParameters.getN());
try {
// 创建无符号大数
BigInteger sm2D = new BigInteger(1,
Hex.decode("b8b08eae2876ef4e24bc7b3e95373b39246cdcce58aaf6cdaf42874369ba1ff3"));
// 创建SM2私钥,ECPrivateKeyParameters实例创建时,会去校验大数是否符合SM2曲线的要求
ECPrivateKeyParameters ecpriv = new ECPrivateKeyParameters(sm2D, gmDomainParameters);
// 生产时,请勿这样使用
ParametersWithRandom fixedRandomParameters = new ParametersWithRandom(ecpriv, new FixedRandom());
// 默认的摘要算法即是SM3
SM2Signer sm2Signer = new SM2Signer(new SM2RSEncoding());
// 此时默认的userid为1234567812345678
sm2Signer.init(true, fixedRandomParameters); // 注意生产时应直接用ecpriv代替fixedRandomParameters
// 添加待签名的数据
sm2Signer.update(new byte[]{0x61, 0x62, 0x63}, 0, 3);
// 生成签名
byte[] signature = sm2Signer.generateSignature();
Assert.assertEquals("edc1431d5871f4f0047775101453f5c7de18ddad9eba7c713fadc23f08e23069b625cf28779efaa432baa3f6682b95534fb6a7fa1c031f2f3778339902f95d66",
Hex.toHexString(signature));
}catch (Exception ex) {
Assert.fail(ex.getMessage());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 默认UserId验签
默认的UserId为:1234567812345678
/**
* SM2验签算法,采用默认的USERID:1234567812345678
*/
@Test
public void testVerifyWithDefaultUserId() {
// 获取国密曲线
X9ECParameters gmParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造Domain参数
ECDomainParameters gmDomainParameters = new ECDomainParameters(gmParameters.getCurve(),
gmParameters.getG(), gmParameters.getN());
try {
// 从压缩公钥中创建点
ECPoint sm2Q = gmDomainParameters.getCurve().decodePoint(
Hex.decode("02a9036e0289d9fa6d566cd0500807e3cba1ce14ba9b58bfbbef00b4b8d502ed72"));
// 跟私钥一样,在创建ECPublicKeyParameters实例的时候,会去校验点是否符合SM2曲线要求
ECPublicKeyParameters ecpub = new ECPublicKeyParameters(sm2Q, gmDomainParameters);
// 默认的摘要算法即是SM3
SM2Signer sm2Signer = new SM2Signer(new SM2RSEncoding());
// 此时默认的userid为1234567812345678
sm2Signer.init(false, ecpub);
// 添加待签名的数据
sm2Signer.update(new byte[]{0x61, 0x62, 0x63}, 0, 3);
// 校验签名
boolean verifyResult = sm2Signer.verifySignature(Hex.decodeStrict("edc1431d5871f4f0047775101453f5c7de18ddad9eba7c713fadc23f08e23069b625cf28779efaa432baa3f6682b95534fb6a7fa1c031f2f3778339902f95d66"));
Assert.assertTrue(verifyResult);
}catch (Exception ex) {
Assert.fail(ex.getMessage());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 自定义UserId签名
UserId可以自行定义,不要超过8192个字节就行
/**
* SM2签名算法,采用自定义的USERID:1234567887654321
*/
@Test
public void testSignWithCustomUserId() {
// 获取国密曲线
X9ECParameters gmParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造Domain参数
ECDomainParameters gmDomainParameters = new ECDomainParameters(gmParameters.getCurve(),
gmParameters.getG(), gmParameters.getN());
try {
// 创建无符号大数
BigInteger sm2D = new BigInteger(1,
Hex.decode("b8b08eae2876ef4e24bc7b3e95373b39246cdcce58aaf6cdaf42874369ba1ff3"));
// 创建SM2私钥,ECPrivateKeyParameters实例创建时,会去校验大数是否符合SM2曲线的要求
ECPrivateKeyParameters ecpriv = new ECPrivateKeyParameters(sm2D, gmDomainParameters);
// 生产时,请勿这样使用
ParametersWithRandom fixedRandomParameters = new ParametersWithRandom(ecpriv, new FixedRandom());
// 自定义userid
ParametersWithID customIdParameters = new ParametersWithID(fixedRandomParameters,
Hex.decodeStrict("31323334353637383837363534333231")); // 注意生产时应直接用ecpriv代替fixedRandomParameters
// 默认的摘要算法即是SM3
SM2Signer sm2Signer = new SM2Signer(new SM2RSEncoding());
// 此时的userid为1234567887654321
sm2Signer.init(true, customIdParameters);
// 添加待签名的数据
sm2Signer.update(new byte[]{0x61, 0x62, 0x63}, 0, 3);
// 生成签名
byte[] signature = sm2Signer.generateSignature();
Assert.assertEquals("732286b4258a1bb7cfb4c8b4156f39661bf1785b48531d521e38b5cb1237c90517e8df1a8c7530c8d813af0c4da784b0e69c125fdef9128b6ff3e0b9242ac850",
Hex.toHexString(signature));
}catch (Exception ex) {
Assert.fail(ex.getMessage());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 自定义UserId验签
/**
* SM2验签算法,采用自定义的USERID:1234567887654321
*/
@Test
public void testVerifyWithCustomUserId() {
// 获取国密曲线
X9ECParameters gmParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造Domain参数
ECDomainParameters gmDomainParameters = new ECDomainParameters(gmParameters.getCurve(),
gmParameters.getG(), gmParameters.getN());
try {
// 从压缩公钥中创建点
ECPoint sm2Q = gmDomainParameters.getCurve().decodePoint(
Hex.decode("02a9036e0289d9fa6d566cd0500807e3cba1ce14ba9b58bfbbef00b4b8d502ed72"));
// 跟私钥一样,在创建ECPublicKeyParameters实例的时候,会去校验点是否符合SM2曲线要求
ECPublicKeyParameters ecpub = new ECPublicKeyParameters(sm2Q, gmDomainParameters);
// 自定义userid
ParametersWithID customIdParameters = new ParametersWithID(ecpub,
Hex.decodeStrict("31323334353637383837363534333231"));
// 默认的摘要算法即是SM3
SM2Signer sm2Signer = new SM2Signer(new SM2RSEncoding());
// 此时的userid为1234567887654321
sm2Signer.init(false, customIdParameters);
// 添加待签名的数据
sm2Signer.update(new byte[]{0x61, 0x62, 0x63}, 0, 3);
// 校验签名
boolean verifyResult = sm2Signer.verifySignature(Hex.decodeStrict("732286b4258a1bb7cfb4c8b4156f39661bf1785b48531d521e38b5cb1237c90517e8df1a8c7530c8d813af0c4da784b0e69c125fdef9128b6ff3e0b9242ac850"));
Assert.assertTrue(verifyResult);
}catch (Exception ex) {
Assert.fail(ex.getMessage());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
未经本人同意,禁止转载!