0%

Crypto.js中的AES加密的参数key是什么意思?

  1. 密钥的位数
  2. 使用字符串作为密钥:PBKDF2算法

注意到使用CryptoJS.AES.encrypt进行加密时,第二个参数key可以为CryptoJS.lib.wordarray,也可以为string,如果是前者,每次加密的结果都一样,如果是后者,每次加密结果都不一致,这是为什么呢?

在CryptoJS的官方文档有这么一句话:

1
2
3
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");

var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");

CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.

意思是如果传入的为一个wordarray,那么会根据位数执行AES-128/AES-192/AES-256的加密算法;如果传入的是一个短语,也就是字符串,那么会根据这个字符串,生成一个256位的密钥。

密钥的位数

如何指定密钥的位数从而指定加密算法呢?手动操作CryptoJS.lib.wordarray吗?很显然这有点困难,这方面的文档也比较少。我们不妨回归算法的本质。

AES加密算法中的128/192/256指的是位数,即bit。将其转换为字节,分别对应16字节、24字节、32字节(分别除以8)。

一个字节能存储的数据长度为2^8 = 256,取值范围为0-255,换算成16进制就是00-FF,这样就提供了一个思路,可以通过构建16进制字符串来指定密钥的位数。在16进制字符串中,2个字符表示1个字节。

crypto-js提供了一个函数:CryptoJS.enc.Hex.parse(),可以将16进制字符串转成wordarray,由此来指定密钥的位数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const CryptoJS = require('crypto-js');

// 长度为16字节的密钥,2个字符表示1字节的16进制数
const hexKeyStr = "AA00BB11CC22DD33EE44FF5566778899";
const hexIvStr = "00112233445566778899aabbccddeeff";

// AES-128 加密
const data = { name: 'test', pwd: '123456' };
const enc = CryptoJS.AES.encrypt(JSON.stringify(data), CryptoJS.enc.Hex.parse(hexKeyStr), {
iv: CryptoJS.enc.Hex.parse(hexIvStr),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(enc.ciphertext.toString());
// 314a0c0af60bf916e1ca49f2d0dcb4cfe2c31a92937ef25e4e12ce9d0951c8f6
console.log(enc.toString());
// MUoMCvYL+Rbhykny0Ny0z+LDGpKTfvJeThLOnQlRyPY=

生成16进制字符串可以使用crypto-js自带的函数:

1
2
3
4
// 生成16字节的word array
const hexWordArray = CryptoJS.lib.WordArray.random(128 / 8);
// 将word array转为16进制字符串
const hexStr = hexWordArray.toString();

使用字符串作为密钥:PBKDF2算法

cryptojs: How to generate AES passphrase

【笔记】PBKDF2算法

一直在思考,为什么传入一个字符串也可以进行加密,而且每次加密的结果都不一样,所谓的自动生成256位密钥又是什么原理,知道看到了Stack Overflow上面的这个问题:cryptojs: How to generate AES passphrase,了解到这背后的原理是PBKDF算法。

PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。

它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。

如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。

在CryptoJS中封装了PBKDF2算法(官方文档 # pbkdf2),使用方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var salt = CryptoJS.lib.WordArray.random(128 / 8);
var key128Bits = CryptoJS.PBKDF2("abcdefg123++", salt, {
keySize: 128 / 32
}).toString();
console.log(key128Bits.length, key128Bits);
// 32 a53fbb9fd7616c8312a16865499170e9

var key256Bits = CryptoJS.PBKDF2("abcdefg123++", salt, {
keySize: 256 / 32
}).toString();
console.log(key256Bits.length, key256Bits);
// 64 a53fbb9fd7616c8312a16865499170e96c2c1eb126641c4a4b540f29d453f56f

var key512Bits = CryptoJS.PBKDF2("abcdefg123++", salt, {
keySize: 512 / 32
}).toString();
console.log(key512Bits.length, key512Bits);
// 128 a53fbb9fd7616c8312a16865499170e96c2c1eb126641c4a4b540f29d453f56f6024b91f301da2e660849f48ce9cad7ddae340bc7b5bcef1944a742cb771e3fb

var key512Bits1000Iterations = CryptoJS.PBKDF2("abcdefg123++", salt, {
keySize: 512 / 32,
iterations: 1000
}).toString();
console.log(key512Bits1000Iterations.length, key512Bits1000Iterations);
// 128 520465b48238dce46d228a9114d9e731e4cae83bab0623dfce4a213fa4d772ad6365cf18f2e71001b432b16b4ab4e2f2c1f417801dcfe168e1226d540167beec

代码解释:

  • 使用CryptoJS.lib.WordArray.random(128 / 8);生成了一个128/8字节长度的椒盐噪声;
  • 使用椒盐噪声和密钥abcdefg123++,通过CryptoJS.PBKDF2生成随机密钥;
  • keySize的设定为“密钥位数/32”,猜测是因为算法中用于运算的数字是32位整型数字;
  • 使用toString()转为16进制字符串,该字符串长度除以2等于字节数,再乘以8得到位数。