国密SM2

SM2非对称加密,基于ECC 椭圆曲线密码机制。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSAECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA

Maven配置:

1
2
3
4
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>

特别关注

对于SM2加解密,需要注意三点,首先需要注意加密处理的密文顺序,关于密钥的使用,以及加解密的HEX转码问题。

加密处理的密文顺序
  • 注意约定C1C2C3的拼装顺序(参照下面加密相关的代码内容,示例顺序为:C1+C2+C3
密钥的使用
  • 有的生成的公钥是带04前缀,有的是不带的。在使用时最对实际情况进行04的截取或补充(示例是带04的)
  • 有的生成的私钥前缀带有00,有的是不带00的,在使用时最对实际情况进行00的截取或补充(示例是不带00的)
HEX转码问题
  • SM2加解密过程中会多次进行HEX的编码何解码,调试时需要注意
  • 在进行HEX编码时,注意中文的编码格式

相关代码

Cipher代码:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class Cipher {
private int ct;
private ECPoint p2;
private SM3Digest sm3keybase;
private SM3Digest sm3c3;
private byte[] key;
private byte keyOff;

public Cipher() {
this.ct = 1;
this.key = new byte[32];
this.keyOff = 0;
}

private void Reset() {
this.sm3keybase = new SM3Digest();
this.sm3c3 = new SM3Digest();

byte[] publicKeyX = HexUtil.byteConvert32Bytes(p2.getX().toBigInteger());
this.sm3keybase.update(publicKeyX, 0, publicKeyX.length);
this.sm3c3.update(publicKeyX, 0, publicKeyX.length);

byte[] publicKeyY = HexUtil.byteConvert32Bytes(p2.getY().toBigInteger());
this.sm3keybase.update(publicKeyY, 0, publicKeyY.length);
this.ct = 1;
NextKey();
}

private void NextKey() {
SM3Digest sm3keycur = new SM3Digest(this.sm3keybase);
sm3keycur.update((byte) (ct >> 24 & 0xff));
sm3keycur.update((byte) (ct >> 16 & 0xff));
sm3keycur.update((byte) (ct >> 8 & 0xff));
sm3keycur.update((byte) (ct & 0xff));
sm3keycur.doFinal(key, 0);
this.keyOff = 0;
this.ct++;
}

public ECPoint Init_enc(SM2 sm2, ECPoint userKey) {
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger k = ecpriv.getD();
ECPoint c1 = ecpub.getQ();
this.p2 = userKey.multiply(k);
Reset();
return c1;
}

public void Encrypt(byte[] data) {
this.sm3c3.update(data, 0, data.length);
for (int i = 0; i < data.length; i++) {
if (keyOff == key.length) {
NextKey();
}
data[i] ^= key[keyOff++];
}
}

public void Init_dec(BigInteger userD, ECPoint c1) {
this.p2 = c1.multiply(userD);
Reset();
}

public void Decrypt(byte[] data) {
for (int i = 0; i < data.length; i++) {
if (keyOff == key.length) {
NextKey();
}
data[i] ^= key[keyOff++];
}
this.sm3c3.update(data, 0, data.length);
}

public void Dofinal(byte[] c3) {
byte[] publicKeyY = HexUtil.byteConvert32Bytes(p2.getY().toBigInteger());
this.sm3c3.update(publicKeyY, 0, publicKeyY.length);
this.sm3c3.doFinal(c3, 0);
Reset();
}
}

SM2代码:

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
42
43
44
45
46
47
48
49
public class SM2 {
public static String[] ECC_PARAM = {
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
};
public final BigInteger ecc_p;
public final BigInteger ecc_a;
public final BigInteger ecc_b;
public final BigInteger ecc_n;
public final BigInteger ecc_gx;
public final BigInteger ecc_gy;
public final ECCurve ecc_curve;
public final ECPoint ecc_point_g;
public final ECDomainParameters ecc_bc_spec;
public final ECKeyPairGenerator ecc_key_pair_generator;
public final ECFieldElement ecc_gx_fieldelement;
public final ECFieldElement ecc_gy_fieldelement;

public SM2() {
this.ecc_p = new BigInteger(ECC_PARAM[0], 16);
this.ecc_a = new BigInteger(ECC_PARAM[1], 16);
this.ecc_b = new BigInteger(ECC_PARAM[2], 16);
this.ecc_n = new BigInteger(ECC_PARAM[3], 16);
this.ecc_gx = new BigInteger(ECC_PARAM[4], 16);
this.ecc_gy = new BigInteger(ECC_PARAM[5], 16);

this.ecc_gx_fieldelement = new Fp(this.ecc_p, this.ecc_gx);
this.ecc_gy_fieldelement = new Fp(this.ecc_p, this.ecc_gy);

this.ecc_curve = new ECCurve.Fp(this.ecc_p, this.ecc_a, this.ecc_b);
this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);

this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.ecc_n);

ECKeyGenerationParameters ecc_ecgenparam;
ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());

this.ecc_key_pair_generator = new ECKeyPairGenerator();
this.ecc_key_pair_generator.init(ecc_ecgenparam);
}

public static SM2 Instance() {
return new SM2();
}
}

SM2Utils代码:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class SM2Utils {
//生成随机秘钥对
public static void generateKeyPair() {
SM2 sm2 = SM2.Instance();
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger privateKey = ecpriv.getD();
ECPoint publicKey = ecpub.getQ();

System.out.println("公钥: " + ByteUtils.toHexString(publicKey.getEncoded()));
System.out.println("私钥: " + ByteUtils.toHexString(privateKey.toByteArray()));
}
//数据加密
public static String encrypt(String publicKey, String plainText) {
if (StringUtils.isBlank(publicKey) || StringUtils.isBlank(plainText)) {
return null;
}
byte[] publicKeyBytes = ByteUtils.fromHexString(publicKey);
byte[] plainTextBytes = ByteUtils.fromHexString(plainText);

byte[] source = new byte[plainTextBytes.length];
System.arraycopy(plainTextBytes, 0, source, 0, plainTextBytes.length);

Cipher cipher = new Cipher();
SM2 sm2 = SM2.Instance();
ECPoint userKey = sm2.ecc_curve.decodePoint(publicKeyBytes);
ECPoint c1 = cipher.Init_enc(sm2, userKey);

cipher.Encrypt(source);
byte[] c3 = new byte[32];
cipher.Dofinal(c3);
//C1 C2 C3拼装成加密字串
return ByteUtils.toHexString(c1.getEncoded()) + ByteUtils.toHexString(source) + ByteUtils.toHexString(c3);
}
//数据解密
public static byte[] decrypt(String privateKey, String cipherText) {
if (StringUtils.isBlank(privateKey) || StringUtils.isBlank(cipherText)) {
return null;
}
byte[] privateKeyBytes = ByteUtils.fromHexString(privateKey);
byte[] cipherTextBytes = ByteUtils.fromHexString(cipherText);
//加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
String data = ByteUtils.toHexString(cipherTextBytes);
/***分解加密字串
* (C1 = C1标志位2位 + C1实体部分128位 = 130)
* (C3 = C3实体部分64位 = 64)
* (C2 = encryptedData.length * 2 - C1长度 - C2长度)
*/
byte[] c1Bytes = ByteUtils.fromHexString(data.substring(0, 130));
int c2Len = cipherTextBytes.length - 97;
byte[] c2 = ByteUtils.fromHexString(data.substring(130, 130 + 2 * c2Len));
byte[] c3 = ByteUtils.fromHexString(data.substring(130 + 2 * c2Len, 194 + 2 * c2Len));

SM2 sm2 = SM2.Instance();
BigInteger userD = new BigInteger(1, privateKeyBytes);
//通过C1实体字节来生成ECPoint
ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
Cipher cipher = new Cipher();
cipher.Init_dec(userD, c1);
cipher.Decrypt(c2);
cipher.Dofinal(c3);
//返回解密结果
return c2;
}
}