为什么 JAVA 里指定算法时,写的是 AES/CBC/PKCS5Padding,每个都是什么含义,又有什么作用。
AES,加解密算法
CBC,数据分组模式
PKCS5Padding,数据按照一定的大小进行分组,最后分剩下那一组,不够长度,就需要进行补齐
简单的说:拿到一个原始数据以后,首先需要对数据进行分组,分组以后如果长度不满足分组条件,需要进行补齐,最后形成多个分组,在使用加解密算法,对这多个分组进行加解密。所以这个过程中,AES,CBC,PKCS5Padding 缺一不可。
PKCS5Padding 和 PKCS7Padding
在对数据进行加解密时,通常将数据按照固定的大小(block size)分成多个组,那么随之就产生了一个问题,如果分到最后一组,不够一个 block size 了,要怎么办?此时就需要进行补齐操作。
补齐规则:The value of each added byte is the number of bytes that are added, i.e. N bytes, each of value N are added.
举例:
36 位的 UUID,如果按照 block size=16 字节(即 128 比特),那么就需要补齐到 48 位,差 12 个字节。那么最后填充的 12 个字节的内容,都是字节表示的 0x0c(即 12)。
填充规则
PKCS5Padding=PKCS7Padding
AES 加解密算法
AES 对称加解密算法,是 2000 年在一堆加解密候选算法中,选出了由比利时科学家发明的 Rijndeal 的分组密码算法。在 AES 的规格中,分组长度固定为 128 比特,秘钥长度只有 128,192 和 256 比特三种。
AES 算法,也是需要经过很多轮的加密和解密。每一轮分为
SubBytes
输入分组是 128 比特,即 16 个字节。16 个字节构成一个 4X4 的正方形。对 16 个字节中的每个字节,从替换表(可以认为是一个 key value 的对照表)中找到对应的替换字节
ShiftRows:对行进行移位
MixColumns:对其中一列(4 个字节)进行 bit 运算
AddRoundKey:使用轮秘钥将前三步的输入,进行异或,得到最终结果
这就最终完成了一轮运算
AES 加解密需要计算多少轮:10 到 14 轮
分组模式 CBC
以上的 AES 算法,仅仅描述了,对于一个数据块,如果进行加密解密。然后在真实世界中,数据大小不一,这就需要对原始数据按照固定的块大小(block size)进行分组。ECB 模式的分组强烈不建议使用,所以简单讨论下 CBC 模式
根据图示,在 CBC 模式下,使用 AES 加解密方式进行分组加解密时,需要用到的两个参数
初始化向量
加解密秘钥(即前面介绍的轮秘钥)
go 编程
// 补齐算法
func PKCSPadding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCSUnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// 注意:本示例中轮秘钥和初始化向量使用相同的字节数据,真实场景不推荐
func AesEncryptCBC(origData, key []byte) ([]byte, error) {
// AES算法的轮秘钥 key
block, err := aes.NewCipher(key)
if err != nil {
log.Errorf(" encrypt error %v", err)
return nil, err
}
blockSize := block.BlockSize()
origData = PKCSPadding(origData, blockSize)
// key[:blockSize]初始化向量
origData = PKCSPadding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
log.Infof(" encrypt base64 string %s", base64.StdEncoding.EncodeToString(crypted))
return crypted, nil
}
func AesDecryptCBC(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
log.Errorf(" encrypt error %v", err)
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCSUnPadding(origData)
return origData, nil
}
复制代码
java 编程
// 注意:本示例中轮秘钥和初始化向量使用相同的字节数据,真实场景不推荐
public static byte[] decrypt(byte[] data, byte[] key) {
try {
// 轮秘钥
SecretKey secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 初始化,设置为解密模式,设置初始化向量
cipher.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(key));
return cipher.doFinal(data);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
复制代码
结论
任何编程语言,原理最重要。不知道原理,只靠拷贝粘贴是没有用的。
评论