7
7
# 概述
8
8
SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥长度128位(16字节),分组大小也是128位(16字节)。在本软件库中,SM4的实现与Go语言中的AES实现一致,也实现了``` cipher.Block ``` 接口,所以,所有Go语言中实现的工作模式(CBC/GCM/CFB/OFB/CTR),都能与SM4组合使用。
9
9
10
- # 工作模式
10
+ # [ 工作模式] ( https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation )
11
+ Go语言实现的工作模式,主要有三类:
12
+ * 基于分组的工作模式 ``` cipher.BlockMode ``` ,譬如CBC。
13
+ * 带有关联数据的认证加密工作模式``` cipher.AEAD ``` ,譬如GCM。
14
+ * 流加密工作模式``` cipher.Stream ``` ,譬如CTR、CFB、OFB。
15
+
11
16
在实际加解密操作中,我们一般不会直接使用``` cipher.Block ``` ,必须结合分组密码算法的工作模式使用。除了Go语言自带的工作模式(CBC/GCM/CFB/OFB/CTR),本软件库也实现了下列工作模式:
12
17
* ECB - 电码本模式
13
18
* BC - 分组链接模式
@@ -19,7 +24,7 @@ SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥
19
24
其中,ECB/BC/HCTR/XTS/OFBNLF是《GB/T 17964-2021 信息安全技术 分组密码算法的工作模式》列出的工作模式。BC/OFBNLF模式是商密中的遗留工作模式,** 不建议** 在新的应用中使用。XTS/HCTR模式适用于对磁盘加密,其中HCTR模式是《GB/T 17964-2021 信息安全技术 分组密码算法的工作模式》最新引入的,HCTR模式最近业界研究比较多,也指出了原论文中的Bugs:On modern processors HCTR [ WFW05] ( https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288 ) is one of the most efficient constructions for building a tweakable super-pseudorandom permutation. However, a bug in the specification and another in Chakraborty and Nandi’s security proof [ CN08] ( https://www.iacr.org/cryptodb/archive/2008/FSE/paper/15611.pdf ) invalidate the claimed security bound.
20
25
不知道这个不足是否会影响到这个工作模式的采用。很奇怪《GB/T 17964-2021 信息安全技术 分组密码算法的工作模式》为何没有纳入GCM工作模式,难道是版权问题?
21
26
22
- 引入CCM模式 ,只是为了有些标准还用到该模式。ECB模式也不建议单独使用。
27
+ 本软件库引入CCM模式 ,只是为了有些标准还用到该模式。ECB模式也不建议单独使用。
23
28
24
29
目前,本软件库的SM4针对ECB/CBC/GCM/XTS工作模式进行了绑定组合性能优化,暂时没有计划使用汇编优化HCTR模式(HCTR模式可以采用和GCM类似的方法进行汇编优化)。
25
30
@@ -49,8 +54,144 @@ SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥
49
54
* 使用预定义的ASN.1结构
50
55
* 和密文简单拼接:譬如CBC工作模式:前面16字节IV,后面ciphertext;GCM模式(使用默认Tag长度和Nonce长度):前面12字节Nonce,后面ciphertext。
51
56
52
- 至于要将二进制转为文本传输、存储,编个码就行:标准base64 / URL base64 / HEX,事先协调、定义好就可以了。
57
+ 至于要将二进制转为文本传输、存储,编个码就行:标准base64 / URL base64 / HEX,事先协调、定义好就可以了。这里顺便推荐一下 [ 性能更好的BASE64实现 ] ( https://github.com/emmansun/base64 ) 。
53
58
59
+ # API文档及示例
60
+ 这里只列出GCM/CBC的例子,其余请参考[ API Document] ( https://godoc.org/github.com/emmansun/gmsm ) 。
61
+
62
+ ## GCM示例
63
+ ``` go
64
+ func Example_encryptGCM () {
65
+ // Load your secret key from a safe place and reuse it across multiple
66
+ // Seal/Open calls. (Obviously don't use this example key for anything
67
+ // real.) If you want to convert a passphrase to a key, use a suitable
68
+ // package like bcrypt or scrypt.
69
+ key , _ := hex.DecodeString (" 6368616e676520746869732070617373" )
70
+ plaintext := []byte (" exampleplaintext" )
71
+
72
+ block , err := sm4.NewCipher (key)
73
+ if err != nil {
74
+ panic (err.Error ())
75
+ }
76
+
77
+ // Never use more than 2^32 random nonces with a given key because of the risk of a repeat.
78
+ nonce := make ([]byte , 12 )
79
+ if _ , err := io.ReadFull (rand.Reader , nonce); err != nil {
80
+ panic (err.Error ())
81
+ }
82
+
83
+ sm4gcm , err := cipher.NewGCM (block)
84
+ if err != nil {
85
+ panic (err.Error ())
86
+ }
87
+
88
+ // You can encode the nonce and ciphertext with your own scheme
89
+ ciphertext := sm4gcm.Seal (nil , nonce, plaintext, nil )
90
+ fmt.Printf (" %x %x \n " , nonce, ciphertext)
91
+ }
92
+
93
+ func Example_decryptGCM () {
94
+ // Load your secret key from a safe place and reuse it across multiple
95
+ // Seal/Open calls. (Obviously don't use this example key for anything
96
+ // real.) If you want to convert a passphrase to a key, use a suitable
97
+ // package like bcrypt or scrypt.
98
+ key , _ := hex.DecodeString (" 6368616e676520746869732070617373" )
99
+ // You can decode the nonce and ciphertext with your encoding scheme
100
+ ciphertext , _ := hex.DecodeString (" b7fdece1c6b3dce9cc386e8bc93df0ce496df789166229f14b973b694a4a23c3" )
101
+ nonce , _ := hex.DecodeString (" 07d168e0517656ab7131f495" )
102
+
103
+ block , err := sm4.NewCipher (key)
104
+ if err != nil {
105
+ panic (err.Error ())
106
+ }
107
+
108
+ sm4gcm , err := cipher.NewGCM (block)
109
+ if err != nil {
110
+ panic (err.Error ())
111
+ }
112
+
113
+ plaintext , err := sm4gcm.Open (nil , nonce, ciphertext, nil )
114
+ if err != nil {
115
+ panic (err.Error ())
116
+ }
117
+
118
+ fmt.Printf (" %s \n " , plaintext)
119
+ // Output: exampleplaintext
120
+ }
121
+ ```
122
+
123
+ ## CBC示例
124
+ ``` go
125
+ func Example_encryptCBC () {
126
+ // Load your secret key from a safe place and reuse it across multiple
127
+ // NewCipher calls. (Obviously don't use this example key for anything
128
+ // real.) If you want to convert a passphrase to a key, use a suitable
129
+ // package like bcrypt or scrypt.
130
+ key , _ := hex.DecodeString (" 6368616e676520746869732070617373" )
131
+ plaintext := []byte (" sm4 exampleplaintext" )
132
+
133
+ block , err := sm4.NewCipher (key)
134
+ if err != nil {
135
+ panic (err)
136
+ }
137
+
138
+ // CBC mode works on blocks so plaintexts may need to be padded to the
139
+ // next whole block. For an example of such padding, see
140
+ // https://tools.ietf.org/html/rfc5246#section-6.2.3.2.
141
+ pkcs7 := padding.NewPKCS7Padding (sm4.BlockSize )
142
+ paddedPlainText := pkcs7.Pad (plaintext)
143
+
144
+ // The IV needs to be unique, but not secure. Therefore it's common to
145
+ // include it at the beginning of the ciphertext.
146
+ ciphertext := make ([]byte , sm4.BlockSize +len (paddedPlainText))
147
+ iv := ciphertext[:sm4.BlockSize ]
148
+ if _ , err := io.ReadFull (rand.Reader , iv); err != nil {
149
+ panic (err)
150
+ }
151
+
152
+ mode := cipher.NewCBCEncrypter (block, iv)
153
+ mode.CryptBlocks (ciphertext[sm4.BlockSize :], paddedPlainText)
154
+
155
+ fmt.Printf (" %x \n " , ciphertext)
156
+ }
157
+
158
+ func Example_decryptCBC () {
159
+ // Load your secret key from a safe place and reuse it across multiple
160
+ // NewCipher calls. (Obviously don't use this example key for anything
161
+ // real.) If you want to convert a passphrase to a key, use a suitable
162
+ // package like bcrypt or scrypt.
163
+ key , _ := hex.DecodeString (" 6368616e676520746869732070617373" )
164
+ ciphertext , _ := hex.DecodeString (" 4d5a1486bfda1b34447afd5bb852e77a867cc6b726a8a0e0ef9b2c21fffc3a30b42acf504628f65cb3fba339101c98ff" )
165
+
166
+ block , err := sm4.NewCipher (key)
167
+ if err != nil {
168
+ panic (err)
169
+ }
170
+
171
+ // The IV needs to be unique, but not secure. Therefore it's common to
172
+ // include it at the beginning of the ciphertext.
173
+ if len (ciphertext) < sm4.BlockSize {
174
+ panic (" ciphertext too short" )
175
+ }
176
+ iv := ciphertext[:sm4.BlockSize ]
177
+ ciphertext = ciphertext[sm4.BlockSize :]
178
+
179
+ mode := cipher.NewCBCDecrypter (block, iv)
180
+
181
+ // CryptBlocks can work in-place if the two arguments are the same.
182
+ mode.CryptBlocks (ciphertext, ciphertext)
183
+
184
+ // Unpad plaintext
185
+ pkcs7 := padding.NewPKCS7Padding (sm4.BlockSize )
186
+ ciphertext, err = pkcs7.Unpad (ciphertext)
187
+ if err != nil {
188
+ panic (err)
189
+ }
190
+
191
+ fmt.Printf (" %s \n " , ciphertext)
192
+ // Output: sm4 exampleplaintext
193
+ }
194
+ ```
54
195
# 性能
55
196
SM4分组密码算法的软件高效实现,不算CPU指令支持的话,已知有如下几种方法:
56
197
* S盒和L转换预计算
@@ -60,6 +201,15 @@ SM4分组密码算法的软件高效实现,不算CPU指令支持的话,已
60
201
61
202
当然,这些与有CPU指令支持的AES算法相比,性能差距依然偏大,要是工作模式不支持并行,差距就更巨大了。
62
203
63
- # API文档及示例
64
- 详见[ API Document] ( https://godoc.org/github.com/emmansun/gmsm ) 。
204
+ # 与KMS集成
205
+ 可能您会说,如果我在KMS中创建了一个SM4对称密钥,就不需要本地加解密了,这话很对,不过有种场景会用到:
206
+ * 在KMS中只创建非对称密钥(KEK);
207
+ * 对称加解密在本地进行;
208
+ * 对称加密密钥,或者称为数据密钥(DEK/CEK),可以在本地通过安全伪随机数函数生成,也可以通过KMS的Data Key API生成(如果有这类API的话),用Data Key API的话,会有DEK/CEK明文传输问题,毕竟KMS需要把DEK/CEK的密文/明文同时返回。
209
+
210
+ 这种加密方案有什么优点呢?
211
+ * KMS API通常都会限流,譬如200次/秒,通过把对称加解密放在本地进行,可以有效减少KMS交互。
212
+ * 减少网络带宽占用。
213
+ * 避免明文数据的网络传输。
65
214
215
+ 当然,前提是用于本地对称加解密的SM4分组密码算法和选用的工作模式性能可以满足需求。
0 commit comments