AES加密有三种规格,如下所示
分组长度的意思是,每次只能加密指定长度,比如AES-128,则每次只能加密16个字节,分若干次加密。
下面按照128位密钥进行说明
下面说明下aes的加密过程,如下所示:
加密过程中,需要将明文放到矩阵里面,密码也会放置于一个矩阵,故对于128位aes而言,每次加密16个字符,密钥也需要是16个字符,如果加密明文不是16的倍数,需要补齐,密钥不足16个字符,也需要补齐。
对于加密内容而言,常用的是pkcs7补齐法,举例如下:
1.明文字符串为123456789,补齐为123456789\a\a\a\a\a\a\a
因为明文9个字符,需要补齐为16个,尚缺少7个字符,故需要补7个7,ascii码7对应的字符为\a;这样加密后,再解密时,得到的字符串123456789\a\a\a\a\a\a\a,最后一个字符为\a,ascii码为7,故按照pkcs7规则,从最后面去掉7个字符,得到原字符串123456789。
2.明文字符串为1234567890,补齐为1234567890\x06\x06\x06\x06\x06\x06
因为明文10个字符,需要补齐为16个,尚缺少6个字符,故需要补6个6,ascii码6对应的字符为\x06。
3.明文1234567890123456,刚好为16个字节,此时依然需要补齐,否则按照pkcs7的规则,解密后得到的明文,要取最后一个字符,ascii是几,就要去掉对应长度的字符。故如果不补齐,会发生错误。明文1234567890123456补齐为1234567890123456\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10,
\x10的ascii代表16。
4.明文为12345678901234561,补齐为12345678901234561\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f
5.明文为空字符串时,补齐为\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10
密码补齐说明:
一般的博客中,没密码补齐之说,本人觉得密码存在补齐之说,就拿go的aes而言,如果密钥不足16个字节,则aes.NewCipher创建密钥时直接报错。
就目前网上的主流做法是,密钥不足16个字节,补齐16个字节,后面补的字符的ascii为0.
下面的网址是一个aes在线加密的例子:
https://tool.lmeee.com/jiami/aes
这个例子里面,密钥是123456789,内部会补7个ascii为0的字符。
我们在写代码,进行密钥补齐的时候,可以自定义,比如针对123456789,可以补齐为1234567897777777,然后用此密钥加密解密,也是可以的。
现在介绍下aes的ecb模式,很明显,对于明文12345678901234561234567890123456,刚好为32字节,再加密的时候,首先会进行补齐操作,后面会多出16个ascii为0的空字符,共48个字符。
加密时,16个字节一次,一共经过了3次,由于前16个字节和中间16个字节都是1234567890123456,故其加密结果应该是一样的,下面的结果中,密文经过base64处理,展示如红色方框,可以看到,在这个长字符串中,并没有发现两段一样的字符串。这不禁让我们对加密结果产生怀疑。
其实这是因为输出结果为base64的缘故,base64是将原来的6位输出为8位,而16*8不是6的倍数,故前面16个字节无法刚好转换成base64编码,一定要借助后面的字符。故base64上没有两段相同的,是正常的。
如果我们将输出结果格式设置为hex,即16进制,效果如下:
很明显,我们可以看到两段一样的字符串,即前面的32个字符和中间的32个字符是一样的。
所以ecb加密方式存在着这种问题:明文段一样,对应的加密结果也一样,存在着安全隐患。
下图即ecb加密模式,若明文1,明文3一样,则密文1和密文3也一样。
为了解决ecb带来的这种问题,引入新的加密模式,后面的明文在加密前,先和前面的加密结果做混合运算,如下所示:
即,第二个明文在加密前,先和第一段明文的密文做混合;
第三个明文加密前,先和第二段明文的密文做混合。
由于第一段明文前面没有密文,故而需要一个初始向量,这种加密模式叫做cbc。
aes本身加密的细节,此处没说,读者可以参考AES加密算法的详细介绍与实现
下面给出aes加密的一个go实现,里面采取的是128为的cbc模式,补齐方式为pkcs7。
对于init函数,本人将里面的代码注释掉了,如果放开,则加密密码就为1663897791,补齐方式跟在线网址的保持了一致。注释的情况下,密码为1663897791666666,不需要补齐。
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
)
//加密过程:
// 1、处理数据,对数据进行填充,采用PKCS7(当密钥长度不够时,缺几位补几个几)的方式。
// 2、对数据进行加密,采用AES加密方法中CBC加密模式
// 3、对得到的加密数据,进行base64加密,得到字符串
// 解密过程相反
//16,24,32位字符串的话,分别对应AES-128,AES-192,AES-256 加密方法
//key不能泄露
//var PwdKey = []byte("ABCDABCDABCDABCD")
var PwdKey = []byte("1663897791666666")
var Iv = []byte("0123456789abcdef")
func init() {
/*
PwdKey[10] = 0
PwdKey[11] = 0
PwdKey[12] = 0
PwdKey[13] = 0
PwdKey[14] = 0
PwdKey[15] = 0
*/
}
//pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
//判断缺少几位长度。最少1,最多 blockSize
padding := blockSize - len(data)%blockSize
//补足位数。把切片[]byte{byte(padding)}复制padding个
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
//pkcs7UnPadding 填充的反向操作
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("加密字符串错误!")
}
//获取填充的个数
unPadding := int(data[length-1])
return data[:(length - unPadding)], nil
}
//AesEncrypt 加密
func AesEncrypt(data []byte, key []byte) ([]byte, error) {
//创建加密实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//判断加密快的大小
blockSize := block.BlockSize()
//填充
encryptBytes := pkcs7Padding(data, blockSize)
//初始化加密数据接收切片
crypted := make([]byte, len(encryptBytes))
//使用cbc加密模式
blockMode := cipher.NewCBCEncrypter(block, Iv)
//执行加密
blockMode.CryptBlocks(crypted, encryptBytes)
return crypted, nil
}
//AesDecrypt 解密
func AesDecrypt(data []byte, key []byte) ([]byte, error) {
//创建实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//获取块的大小
//blockSize := block.BlockSize()
//使用cbc
blockMode := cipher.NewCBCDecrypter(block, Iv)
//初始化解密数据接收切片
crypted := make([]byte, len(data))
//执行解密
blockMode.CryptBlocks(crypted, data)
//去除填充
crypted, err = pkcs7UnPadding(crypted)
if err != nil {
return nil, err
}
return crypted, nil
}
//EncryptByAes Aes加密 后 base64 再加
func EncryptByAes(data []byte) (string, error) {
res, err := AesEncrypt(data, PwdKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(res), nil
}
//DecryptByAes Aes 解密
func DecryptByAes(data string) ([]byte, error) {
dataByte, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, err
}
return AesDecrypt(dataByte, PwdKey)
}
func main() {
encryptStr, err := EncryptByAes([]byte("1234567890"))
if err == nil {
var decryptByte []byte
decryptByte, err = DecryptByAes(encryptStr)
fmt.Println(string(decryptByte))
}
}