在前面文章我们已经可以制作SM2证书了,主要应用了SM2签名验证算法和SM3摘要算法,在本文中主要介绍SM2公钥加密算法。这里我们使用SM2数字证书来做SM2非对称加密,然后使用硬件加密设备做解密,比如加密文件只能由指定的智能密码钥匙UKey才能解开。
SM2加密算法
SM2加密同样使用接收方公钥加密,公钥由一个曲线坐标点组成,在X.509证书中的共钥表示为04标记开始的2个32byte的BigInteger,即曲线点P(x,y)。SM2公钥加密算法比RSA相对复杂,加密结果由3个部分组成,SM2加密过程中使用了随机数,因此同样的明文数据每一次加密结果都不一样。SM2加密算法流程如下图所示。
根据国密推荐的SM2椭圆曲线公钥密码算法,首先产生随机数计算出曲线点C1,2个32byte的BigInteger大数,即为SM2加密结果的第1部分。第2部分则是真正的密文,是对明文的加密结果,长度和明文一样。第3部分是杂凑值,用来效验数据。按国密推荐的256位椭圆曲线,明文加密结果比原长度会大96byte。
SM2加密算法同样也可以基于使用BouncyCastle库实现。一般使用数字证书来标识身份,同时使用证书中公钥加密数据。如下SM2Cipher类是C#下SM2软算法实现。
SM2Cipher.cs
1public class SM2Cipher
2...{
3private int ct = 1;
4
5private ECPoint p2;
6private SM3Digest sm3keybase;
7private SM3Digest sm3c3;
8
9private byte[] key = new byte[32];
10private byte keyOff = 0;
11
12public SM2Cipher() ...{ }
13
14
15
16private void Reset()
17...{
18sm3keybase = new SM3Digest();
19sm3c3 = new SM3Digest();
20
21byte[] p;
22
23p = p2.X.ToBigInteger().ToByteArrayUnsigned();
24sm3keybase.BlockUpdate(p, 0, p.Length);
25sm3c3.BlockUpdate(p, 0, p.Length);
26
27p = p2.Y.ToBigInteger().ToByteArrayUnsigned();
28sm3keybase.BlockUpdate(p, 0, p.Length);
29
30ct = 1;
31NextKey();
32}
33
34private void NextKey()
35...{
36SM3Digest sm3keycur = new SM3Digest(sm3keybase);
37sm3keycur.Update((byte)(ct >> 24 & 0x00ff));
38sm3keycur.Update((byte)(ct >> 16 & 0x00ff));
39sm3keycur.Update((byte)(ct >> 8 & 0x00ff));
40sm3keycur.Update((byte)(ct & 0x00ff));
41sm3keycur.DoFinal(key, 0);
42keyOff = 0;
43ct++;
44}
45
46public virtual ECPoint InitEncipher(ECPoint userKey)
47...{
48BigInteger k = null;
49ECPoint c1 = null;
50
51if (1==1)
52...{
53AsymmetricCipherKeyPair key = SM2CryptoServiceProvider.SM2KeyPairGenerator.GenerateKeyPair();
54ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)key.Private;
55ECPublicKeyParameters ecpub = (ECPublicKeyParameters)key.Public;
56
57k = ecpriv.D;
58c1 = ecpub.Q;
59}
60
61p2 = userKey.Multiply(k);
62Reset();
63
64return c1;
65
66}
67
68public virtual void Encrypt(byte[] data)
69...{
70sm3c3.BlockUpdate(data, 0, data.Length);
71for (int i = 0; i < data.Length; i++)
72...{
73if (keyOff == key.Length)
74NextKey();
75
76data[i] ^= key[keyOff++];
77}
78}
79
80public virtual void InitDecipher(BigInteger userD, ECPoint c1)
81...{
82p2 = c1.Multiply(userD);
83Reset();
84}
85
86public virtual void Decrypt(byte[] data)
87...{
88for (int i = 0; i < data.Length; i++)
89...{
90if (keyOff == key.Length)
91NextKey();
92
93data[i] ^= key[keyOff++];
94}
95sm3c3.BlockUpdate(data, 0, data.Length);
96}
97
98public virtual void Dofinal(byte[] c3)
99...{
100byte[] p = p2.Y.ToBigInteger().ToByteArrayUnsigned();
101sm3c3.BlockUpdate(p, 0, p.Length);
102sm3c3.DoFinal(c3, 0);
103Reset();
104}
105
106
107/**//// <summary>
108/// 使用SM2公钥加密数据
109/// </summary>
110/// <param name="pubKey"></param>
111/// <param name="plaintext"></param>
112/// <returns></returns>
113public string Encrypt(ECPoint pubKey, byte[] plaintext)
114...{
115
116byte[] data = new byte[plaintext.Length];
117Array.Copy(plaintext, data, plaintext.Length);
118
119ECPoint c1 = InitEncipher(pubKey);
120Encrypt(data);
121
122byte[] c3 = new byte[32];
123Dofinal(c3);
124
125string hexString = c1.X.ToBigInteger().ToString(16) + c1.Y.ToBigInteger().ToString(16)
126+ Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(data)
127+ Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(c3);
128
129return hexString;
130
131}
132
133/**//// <summary>
134/// 使用SM2解密数据
135/// </summary>
136/// <param name="privateKey"></param>
137/// <param name="ciphertext"></param>
138/// <returns></returns>
139public byte[] Decrypt(BigInteger privateKey, string ciphertext)
140...{
141
142string hexString = ciphertext;
143string c1X = hexString.Substring(0, 64);
144string c1Y = hexString.Substring(0 + c1X.Length, 64);
145string encrypData = hexString.Substring(c1X.Length + c1Y.Length, hexString.Length - c1X.Length - c1Y.Length - 64);
146string c3 = hexString.Substring(hexString.Length - 64);
147
148byte[] data = SM2CryptoServiceProvider.StrToToHexByte(encrypData);
149
150ECPoint c1 = CreatePoint(c1X, c1Y);
151![]()
152InitDecipher(privateKey, c1);
153Decrypt(data);
154
155byte[] c3_ = new byte[32];
156Dofinal(c3_);
157
158string decryptData = Encoding.Default.GetString(data);
159bool isDecrypt = Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(c3_) == c3;
160
161return (isDecrypt ? data : new byte[0]);
162}
163
164
165/**//// <summary>
166/// 创建坐标点
167/// </summary>
168/// <param name="x"></param>
169/// <param name="y"></param>
170/// <returns></returns>
171public static ECPoint CreatePoint(string x, string y)
172...{
173
174BigInteger biX = new BigInteger(x, 16);
175BigInteger biY = new BigInteger(y, 16);
176ECFieldElement fx = new FpFieldElement(SM2CryptoServiceProvider.ecc_p, biX);
177ECFieldElement fy = new FpFieldElement(SM2CryptoServiceProvider.ecc_p, biY);
178ECPoint point = new FpPoint(SM2CryptoServiceProvider.ecc_curve, fx,fy);
179return point;
180}
181
182
183
184}
SM2解密算法
SM2解密算法是加密逆运算。首先需要从密文中取出加密结果的3部分值,然后通过私钥计算出 M'明文值,最后效验数据。SM2解密算法流程如下图所示。
SM2解密同样也可以使用软算法实现。但因为涉及到私钥运算,为保护私钥安全,建议在硬件设备中运行,例如UKey等存储介质这样可以更好的保护密钥安全。拿文件加密来说,首先拿UKey里面的加密证书加密,这部分可在应用系统内完成。解密的话则需要加密证书对应UKey才能做解密,由应用系统调用UKey解密接口,在物理硬件内完成数据解密,同时可以受设备Ping码保护。