写在前面
最近在写一个项目,大致内容就是通过配置json模版数据实现快速调整活动板块,json串在简单的序列化后传输时,安全性是一个问题,那么加密就是必不可少的一步,这里我采用的是AES算法搭配CBC模式进行加解密
AES算法使用固定长度的密钥(128位、192位或256位)和固定长度的分组(128位)进行加密和解密。加密过程中,明文被分成多个128位的分组,然后通过多轮的迭代,每轮包括替代、置换和混淆步骤,最终得到密文。解密过程与加密过程相反,通过逆向操作将密文还原为明文。首先我们要了解一些必需的知识:
五种常见的模式
分组密码是每次只能处理特定长度的一块数据的算法,每块都是一个分组,分组的比特数就称为分组长度,但是当加密的内容超过分组密码的分组长度时,就要对分组密码算法进行迭代,迭代的方法称为分组密码的模式。
ECB模式(电子密码本模式)
ECB模式是将明文消息分成固定大小的分组,当最后一个分组的内容小于分组长度时,需要用特定的数据进行填充以至于长度等于分组长度,每个分组的加密和解密都是独立的,可以进行并行操作,但是安全性较低
CBC模式(密码分组链接模式)
CBC模式中的第一个分组需要用初始化向量IV(一个随机的且长度为一个分组长度的比特序列)进行异或操作再进行加密,而后面的每一个分组都要先和前一个分组加密后的密文分组进行异或操作,然后再加密。加密是连续的,不能进行并行操作,增加了分组之间的关联性
CFB模式(密文反馈模式)
CFB模式是将前一个分组的密文加密后和当前分组的明文进行异或操作生成当前分组的密文,第一个明文分组通过初始化向量lV进行加密再与之进行异或操作得到第一个密文分组。
OFB模式(输出反馈模式)
OFB模式是通过将明文分组和密码算法的输出进行异或操作来产生密文分组的,也需要使用初始化向量(IV)
CTR模式(计数器模式)
在CTR模式中,每次加密时都会生成一个不同的值来作为计数器的初始值,每个分组对应一个逐次累加的计数器,通过对计数器进行加密来生成密钥流,再将密钥流与明文分组进行异或操作得到密文分组
填充方案
在数据加解密应用中,数据填充又是其中重要的组成部分。数据填充通常有两个作用: 一是按要求将数据补足到要求的块长度来满足加密算法的应用需求;二是通过增加填充数据来进一步提高密文的安全性。
-
PKCS7(Padding Cryptography System 7)填充:在明文末尾添加填充字节,填充字节的值等于需要填充的字节数。
-
PKCS5(Padding Cryptography System 5)填充:与PKCS7填充类似,但用于8字节分组的加密算法。
-
NoPadding填充:不进行任何填充,要求明文长度必须是分组长度的整数倍。
由于我们采用的是PKCS7填充,那么我这里详细举例说明PKCS7的实现。PKCS7的填充方式为当数据长度不足数据块长度时,缺几位补几个几
例:对于AES128算法其数据块为16Byte(数据长度需要为16Byte的倍数),如果数据为“00112233445566778899AA”一共11个Byte,缺了5位,采用PKCS7方式填充之后的数据为“00112233445566778899AA0505050505”
注意的一点是:如果是数据刚好满足数据块长度也要在元数据后再按PKCS7规则填充一个数据块数据,这样做的目的是为了区分有效数据和补齐数据。
仍以AES128为例:如果数据为”00112233445566778899AABBCCDDEEFF”一共16个符合数据块规则采用PKCS7方式填充之后的数据为“00112233445566778899AABBCCDDEEFF10101010101010101010101010101010”
话不多说上代码
//生成随机的16字节的初始化向量 (IV)
//对于iv不需要保密,只要随机即可
func init() {
iv = make([]byte, 16)
_, err := io.ReadFull(rand.Reader, iv)
if err != nil {
panic(err)
}
}
var iv []byte
// EncryptJSON 加密使用 AES-256 算法,CBC 模式加密
// 填充秘钥key的16位,24,32分别对应AES-128, AES-192, or AES-256.
// data: 加密目标字符串
// key: 加密Key
func EncryptJSON(data string, key string) (string, error) {
encrypted, err := aesCBCEncrypt([]byte(data), []byte(key), iv)
if err != nil {
return "", err
}
//进行base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptJSON 解密
// encrypted: 解密目标字符串
// key: 加密Key
func DecryptJSON(encrypted string, key string) (string, error) {
data, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
//解密的iv要和加密的iv一致
decrypted, err := aesCBCDecrypt(data, []byte(key), iv)
if err != nil {
return "", err
}
return string(decrypted), nil
}
// aesCBCEncrypt AES/CBC/PKCS7Padding 加密
func aesCBCEncrypt(plaintext []byte, key []byte, iv []byte) ([]byte, error) {
// AES,创建加密器,返回AES算法的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 填充
plaintext = paddingPKCS7(plaintext, aes.BlockSize)
// 选择分组加密模式--CBC加密
// iv的长度必须和block的长度相同
cbc := cipher.NewCBCEncrypter(block, iv)
cbc.CryptBlocks(plaintext, plaintext)
return plaintext, nil
}
// aesCBCDecrypt AES/CBC/PKCS7Padding 解密
func aesCBCDecrypt(ciphertext []byte, key []byte, iv []byte) ([]byte, error) {
// AES
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
// CBC 解密
cbc := cipher.NewCBCDecrypter(block, iv)
cbc.CryptBlocks(ciphertext, ciphertext)
// PKCS7 反填充
result := unPaddingPKCS7(ciphertext)
return result, nil
}
// PKCS7 填充
func paddingPKCS7(plaintext []byte, blockSize int) []byte {
paddingSize := blockSize - len(plaintext)%blockSize
//对原来的明文填充paddingSize个[]byte{byte(paddingSize)}
paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)
//fmt.Println(paddingText)
return append(plaintext, paddingText...)
}
// PKCS7 反填充
func unPaddingPKCS7(s []byte) []byte {
length := len(s)
if length == 0 {
return s
}
//取出密文的最后一个字节
//为什么是最后一个字节看上文填充规则部分
unPadding := int(s[length-1])
//删除填充
return s[:(length - unPadding)]
}
进行测试
func TestEncryptJSON(t *testing.T) {
jsonString := `{
"name":"json加密测试",
"url":"https://github.com/lsy88/jsonwizard",
"address":{
"city":"新乡",
"country":"中国"
},
"arrayBrowser":[{
"name":"Google",
"url":"http://www.google.com"
},
{
"name":"Baidu",
"url":"http://www.baidu.com"
},
{
"name":"SoSo",
"url":"http://www.SoSo.com"
}]
}`
json, err := EncryptJSON(jsonString, "list123111111111list123111111111")
if err != nil {
t.Error(err)
}
decryptJSON, err := DecryptJSON(json, "list123111111111list123111111111")
if err != nil {
t.Error(err)
}
if decryptJSON != jsonString {
t.Error("failed")
} else {
t.Log(json)
t.Log("success")
}
}
//encrypt_test.go:39: 5IruMLfqiAwY5gG7+CvjNnlTkh+XUzDNMtVurN68rKMqK6G1EeM4O1UnSvjsVc9H+LT/FKHqJqN7jq7vb/TlvbQX9FrTO8aKeqEZSWuYLblvy3m8ZchkipjBoaHO1q3RT3Dfv+cqbkFtjEVDDbryhKzxkGOS7eFwM7apsO5pw8NL7w42PXcJxJGXmbbM8SjHonjx/RC1k3i9JM5rQrP00m8xyVwD7xg1R80SB1M8yNrS4H8nAI9Fd5KZBTnW7V7jemekeSb4uiFMB4F3n4NIrKL4FS7Kj//IbNd3EiQzbWKkbOcZeVtKtD87HrIac0Gtg/B+WFsFFQR0AOLS+Rg3PxR9u8DYvXzsl5inTnUja/O/48o8W4/0cn8eubW3Ai2630J3/OXECFglYUqf9qQWmUSKxeJAV22mtpez+bifQzIZgj5BLbfpFWrRa/myfLdY
//success