AES 加密 CBC 模式
参考文档: https://juejin.cn/post/6953493716078166030
什么是CBC模式
密码分组链接模式 CBC (Cipher Block Chaining),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
这时候就有个问题,那第一段的明文怎么加密呢?这时候就引入了初始化向量(英语:initialization vector,缩写为IV)。
初始化向量是随机的,就是你可以自定义这个初始化向量,不同的初始化向量加密出来的结果也不一样。
Go 实现
在 Go 中,我们可以用官方提供的 crypto/aes 标准库来给我们进行 AES 加密,不过这个库并没有给我们指定加密模式,需要我们自己通过 crypto/cipher 来选择加密模式。
AES CBC 模式加密
首先我们可以调用 crypto/aes
的函数来返回一个密码块
func NewCipher(key []byte) (cipher.Block, error)
NewCipher 创建并返回一个新的 cipher.Block。 key参数应为 16、24 或 32 个字节长度的 AES 密钥,以选择 AES-128,AES-192 或AES-256。
比如我们要选择 AES-128 进加密,这时候就可以这么写
key := "qwertyuiopasdfgh"
block,err:=aes.NewCipher([]byte(key))
当然,密钥 key 是自定义的,你可以更改你喜欢的密钥。
然后比如说我们要加密 oooooo灰灰
这个文本内容,首先就是对这个文本内容进行填充,这个需要我们自己实现,我们可以定义一个 PKCS7Padding 的填充方法,用 PKCS7Padding 填充模式进行填充内容。
PKCS7Padding填充模式:假设数据长度需要填充 n(n>0) 个字节才对齐,那么填充 n 个字节,每个字节都是 n 。如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小
/*
PKCS7Padding 填充模式
text:明文内容
blockSize:分组块大小
*/
func PKCS7Padding(text []byte,blockSize int) []byte {
// 计算待填充的长度
padding := blockSize - len(text)%blockSize
var paddingText []byte
if padding == 0 {
// 已对齐,填充一整块数据,每个数据为 blockSize
paddingText = bytes.Repeat([]byte{byte(blockSize)},blockSize)
}else {
// 未对齐 填充 padding 个数据,每个数据为 padding
paddingText = bytes.Repeat([]byte{byte(padding)},padding)
}
return append(text,paddingText...)
}
填充完后,我们就可以用 crypto/cipher
的 NewCBCEncrypter
函数来得到一个BlockMode
,然后用BlockMode
来进行加密。具体代码如下:
/*
CBC 加密
text 待加密的明文
key 秘钥
*/
func CBCEncrypter(text []byte,key []byte,iv []byte) []byte{
block,err:=aes.NewCipher(key)
if err !=nil {
fmt.Println(err)
}
// 填充
paddText := PKCS7Padding(text,block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block,iv)
// 加密
result := make([]byte,len(paddText))
blockMode.CryptBlocks(result,paddText)
// 返回密文
return result
}
AES CBC 模式解密
解密的话 Go 官方标准库 crypto/cipher
为我们提供了 NewCBCDecrypter
函数,我们可以通过此函数来获得BlockMode
,然后进行解密
/*
CBC 解密
encrypter 待解密的密文
key 秘钥
*/
func CBCDecrypter(encrypter []byte,key []byte,iv []byte) []byte {
block,err:=aes.NewCipher(key)
if err !=nil {
fmt.Println(err)
}
blockMode := cipher.NewCBCDecrypter(block,iv)
result := make([]byte,len(encrypter))
blockMode.CryptBlocks(result,encrypter)
// 去除填充
result = UnPKCS7Padding(result)
return result
}
上面去除填充的 UnPKCS7Padding
函数,我们可以通过在末尾填充的数据来获取到底填充了多少长度的数据。因为 PKCS7Padding 填充数据的原理是假设数据长度需要填充 n(n>0) 个字节才对齐,那么填充 n 个字节,每个字节都是 n 。如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小
/*
去除 PKCS7Padding 填充的数据
text 待去除填充数据的原文
*/
func UnPKCS7Padding(text []byte) []byte{
// 取出填充的数据 以此来获得填充数据长度
unPadding := int(text[len(text)-1])
return text[:(len(text)-unPadding)]
}
完整代码
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)
func main() {
text := "oooooo灰灰"
key := "qwertyuiopasdfgh"
// 初始向量
iv:=[]byte("qwertyuiopasdfgh")
// 加密
result := CBCEncrypter([]byte(text),[]byte(key),iv)
// 解密
fmt.Println(string(CBCDecrypter(result,[]byte(key),iv)))
}
/*
CBC 加密
text 待加密的明文
key 秘钥
*/
func CBCEncrypter(text []byte,key []byte,iv []byte) []byte{
block,err:=aes.NewCipher(key)
if err !=nil {
fmt.Println(err)
}
// 填充
paddText := PKCS7Padding(text,block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block,iv)
// 加密
result := make([]byte,len(paddText))
blockMode.CryptBlocks(result,paddText)
// 返回密文
return result
}
/*
CBC 解密
encrypter 待解密的密文
key 秘钥
*/
func CBCDecrypter(encrypter []byte,key []byte,iv []byte) []byte {
block,err:=aes.NewCipher(key)
if err !=nil {
fmt.Println(err)
}
blockMode := cipher.NewCBCDecrypter(block,iv)
result := make([]byte,len(encrypter))
blockMode.CryptBlocks(result,encrypter)
// 去除填充
result = UnPKCS7Padding(result)
return result
}
/*
PKCS7Padding 填充模式
text:明文内容
blockSize:分组块大小
*/
func PKCS7Padding(text []byte,blockSize int) []byte {
// 计算待填充的长度
padding := blockSize - len(text)%blockSize
var paddingText []byte
if padding == 0 {
// 已对齐,填充一整块数据,每个数据为 blockSize
paddingText = bytes.Repeat([]byte{byte(blockSize)},blockSize)
}else {
// 未对齐 填充 padding 个数据,每个数据为 padding
paddingText = bytes.Repeat([]byte{byte(padding)},padding)
}
return append(text,paddingText...)
}
/*
去除 PKCS7Padding 填充的数据
text 待去除填充数据的原文
*/
func UnPKCS7Padding(text []byte) []byte{
// 取出填充的数据 以此来获得填充数据长度
unPadding := int(text[len(text)-1])
return text[:(len(text)-unPadding)]
}
总结
尽管 CBC 模式是 ECB 的改良版,但是它还是有一个 bug,问题出在初始变量 iv
上。