今天修改一个项目的时候需要用到 AES 加密。为此重新学习了下 Go 中的以下两个包
crypto/aes crypto/cipher
然后从网上随便抄了一段代码依葫芦画瓢写了一下代码,如下
package main import ( "crypto/aes" "crypto/cipher" "encoding/hex" "bytes" "fmt" ) func PKCS7Padding(ciphertext []byte) []byte { padding := aes.BlockSize - len(ciphertext) % aes.BlockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } func PKCS7UnPadding(plantText []byte) []byte { length := len(plantText) unpadding := int(plantText[length-1]) return plantText[:(length - unpadding)] } func main() { key, _ := hex.DecodeString("6368616e676520746869732070617373") iv, _ := hex.DecodeString("3b9e61ed65ec555f43f9fcb41d5dde3a") plaintext := []byte("我是 password ") // -------- 加密开始--------- plaintext = PKCS7Padding(plaintext) ciphertext := make([]byte,len(plaintext)) block, err := aes.NewCipher(key) if err != nil { panic(err) } mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(ciphertext, plaintext) fmt.Printf("%x\n", ciphertext) // ----------------解密开始--------- mode = cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(ciphertext, ciphertext) ciphertext = PKCS7UnPadding(ciphertext) fmt.Printf("%s\n", ciphertext) }
运行结果如下
go run main.go 711164f97fa824d7695698b73ee5dc4f97e64dd67106b47d8e50a6a388e55ea3 我是 password
PKCS7Padding
和 PKCS5Padding
的区别
关于 PKCS7Padding
和 PKCS5Padding
的区别,从某些方面说是没区别的,因为它们的填充算法都一样
value = K - (L mod K) K=块大小 L=数据长度
比如,如果 L=8
, 则需要填充额外的 8个 byte 的 8
说起来唯一的区别就是:PKCS5Padding
是 PKCS7Padding
的子集
因为在 PKCS5Padding
中,明确定义 BlockSize 的大小(也就是 K )是 8
位。而在 PKCS7Padding 定义中,对于块的大小是不确定的,可以在 1-255之间(块长度超出 255的尚待研究),但一般都设置为 AES 块的默认大小,也就是 16
这样子。
const BlockSize = 16
16 指的是 byte 的大小,如果换成十六进制则是 32,一个 byte = 2 个 hex 字符
填充值的算法都是一样的:
iv
iv
被称为 初始化向量,如果你不理解它也没关系,你只要知道,iv
不同,即使加密密钥相同得出的结果也是不同的。
为了防止密钥被猜出来,我们可以随机化 iv
iv := make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) }
然后,我们可以把 iv
和加密后的数据一起返回给客户端
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "bytes" "fmt" "io" ) func PKCS7Padding(ciphertext []byte) []byte { padding := aes.BlockSize - len(ciphertext) % aes.BlockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } func PKCS7UnPadding(plantText []byte) []byte { length := len(plantText) unpadding := int(plantText[length-1]) return plantText[:(length - unpadding)] } func main() { key, _ := hex.DecodeString("6368616e676520746869732070617373") plaintext := []byte("我是 password ") // ----------------加密开始--------- plaintext = PKCS7Padding(plaintext) // 多申请 aes.BlockSize 长度用于存储 iv ciphertext := make([]byte,aes.BlockSize + len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } fmt.Printf("iv:%x\n",iv) block, err := aes.NewCipher(key) if err != nil { panic(err) } mode := cipher.NewCBCEncrypter(block, iv) // 注意偏移 aes.BlockSize mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) hexciphertext := hex.EncodeToString(ciphertext) fmt.Printf("%s\n", hexciphertext) // ----------------解密开始--------- byteciphertext, _ := hex.DecodeString(hexciphertext) deciphertext := make([]byte,len(hexciphertext)) iv = byteciphertext[0:aes.BlockSize] mode = cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(deciphertext, byteciphertext[aes.BlockSize:]) deciphertext = PKCS7UnPadding(deciphertext) fmt.Printf("%s\n", deciphertext) }
运行结果如下
iv:cf29f14fad6e10c098fe8c808b4bceae cf29f14fad6e10c098fe8c808b4bceae13a240b03ae254232d2d9b1a1837f762517be655462fd4f6f67d6d1719d4abdd 我是 password
可以看到,每次运行的结果都不一样,这是因为 iv
不一样
后记:其实还有另外两种填充方式 NoPadding
和 ZeroPadding
前者是不填充,什么数据就是什么数据,后者则是在 不不组 BlockSize 时填充 0
,注意,这个 0
不过,为了兼容 JAVA,请谨慎选择填充方式,如果没有特别理由,请选择 PKCS5Padding