Skip to content

Commit 2187bea

Browse files
authored
doc: 增加与KMS集成这一节及其它
1 parent 015ca1b commit 2187bea

File tree

1 file changed

+155
-5
lines changed

1 file changed

+155
-5
lines changed

docs/sm4.md

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
# 概述
88
SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥长度128位(16字节),分组大小也是128位(16字节)。在本软件库中,SM4的实现与Go语言中的AES实现一致,也实现了```cipher.Block```接口,所以,所有Go语言中实现的工作模式(CBC/GCM/CFB/OFB/CTR),都能与SM4组合使用。
99

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+
1116
在实际加解密操作中,我们一般不会直接使用```cipher.Block```,必须结合分组密码算法的工作模式使用。除了Go语言自带的工作模式(CBC/GCM/CFB/OFB/CTR),本软件库也实现了下列工作模式:
1217
* ECB - 电码本模式
1318
* BC - 分组链接模式
@@ -19,7 +24,7 @@ SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥
1924
其中,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.
2025
不知道这个不足是否会影响到这个工作模式的采用。很奇怪《GB/T 17964-2021 信息安全技术 分组密码算法的工作模式》为何没有纳入GCM工作模式,难道是版权问题?
2126

22-
引入CCM模式,只是为了有些标准还用到该模式。ECB模式也不建议单独使用。
27+
本软件库引入CCM模式,只是为了有些标准还用到该模式。ECB模式也不建议单独使用。
2328

2429
目前,本软件库的SM4针对ECB/CBC/GCM/XTS工作模式进行了绑定组合性能优化,暂时没有计划使用汇编优化HCTR模式(HCTR模式可以采用和GCM类似的方法进行汇编优化)。
2530

@@ -49,8 +54,144 @@ SM4分组密码算法,其地位类似NIST中的AES分组密码算法,密钥
4954
* 使用预定义的ASN.1结构
5055
* 和密文简单拼接:譬如CBC工作模式:前面16字节IV,后面ciphertext;GCM模式(使用默认Tag长度和Nonce长度):前面12字节Nonce,后面ciphertext。
5156

52-
至于要将二进制转为文本传输、存储,编个码就行:标准base64 / URL base64 / HEX,事先协调、定义好就可以了。
57+
至于要将二进制转为文本传输、存储,编个码就行:标准base64 / URL base64 / HEX,事先协调、定义好就可以了。这里顺便推荐一下[性能更好的BASE64实现](https://github.com/emmansun/base64)
5358

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+
```
54195
# 性能
55196
SM4分组密码算法的软件高效实现,不算CPU指令支持的话,已知有如下几种方法:
56197
* S盒和L转换预计算
@@ -60,6 +201,15 @@ SM4分组密码算法的软件高效实现,不算CPU指令支持的话,已
60201

61202
当然,这些与有CPU指令支持的AES算法相比,性能差距依然偏大,要是工作模式不支持并行,差距就更巨大了。
62203

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+
* 避免明文数据的网络传输。
65214

215+
当然,前提是用于本地对称加解密的SM4分组密码算法和选用的工作模式性能可以满足需求。

0 commit comments

Comments
 (0)