注意到使用
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 | const CryptoJS = require('crypto-js'); |
生成16进制字符串可以使用crypto-js
自带的函数:
1 | // 生成16字节的word array |
使用字符串作为密钥:PBKDF2算法
一直在思考,为什么传入一个字符串也可以进行加密,而且每次加密的结果都不一样,所谓的自动生成256位密钥又是什么原理,知道看到了Stack Overflow上面的这个问题:cryptojs: How to generate AES passphrase,了解到这背后的原理是PBKDF算法。
PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。
它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。
如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。
在CryptoJS中封装了PBKDF2算法(官方文档 # pbkdf2),使用方法如下所示:
1 | var salt = CryptoJS.lib.WordArray.random(128 / 8); |
代码解释:
- 使用
CryptoJS.lib.WordArray.random(128 / 8);
生成了一个128/8
字节长度的椒盐噪声; - 使用椒盐噪声和密钥
abcdefg123++
,通过CryptoJS.PBKDF2
生成随机密钥; keySize
的设定为“密钥位数/32”,猜测是因为算法中用于运算的数字是32位整型数字;- 使用
toString()
转为16进制字符串,该字符串长度除以2等于字节数,再乘以8得到位数。