CryptoJS中AES加密的一个大坑

今天在写东西的时候遇到了一个CryptoJS的大坑,没有深入理解AES等对称加密原理的很容易掉到坑里。

CryptoJS是前端最好用的加密框架,然而这个加密框架在处理AES加密的时候有一个大坑。

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
26
27
28
29
30
31
32
33
34
35
/**
* Encrypts a message using a password.
*
* @param {Cipher} cipher The cipher algorithm to use.
* @param {WordArray|string} message The message to encrypt.
* @param {string} password The password.
* @param {Object} cfg (Optional) The configuration options to use for this operation.
*
* @return {CipherParams} A cipher params object.
*
* @static
*
* @example
*
* var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password');
* var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL });
*/
encrypt: function (cipher, message, password, cfg) {
// Apply config defaults
cfg = this.cfg.extend(cfg);

// Derive key and other params
var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize);

// Add IV to config
cfg.iv = derivedParams.iv;

// Encrypt
var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg);

// Mix in derived params
ciphertext.mixIn(derivedParams);

return ciphertext;
}

CryptoJS的这一部分源码是这个样子的,如果你传入的key实际上是个字符串的话,那么它实际上使用的是这一套代码。

在这个代码里传入的key实际上是这个代码中的password参数,它会被CryptoJS.lib.PasswordBasedCipher处理,并用cfg.kdf生成一个新的WordArray,这个生成出来的东西才是实际的key。这个生成出来的东西是具有随机性的,每次都不一样。

在这种case下CryptoJS中使用到的key和Java等语言中AES加密的key不一样,它们并不是一个东西,这也正是为什么很多在线加密工具箱AES加密功能加密出来的结果是错误的,而且每点一次加密出来的结果不一样。

实际上这些工具箱在源码上给CryptoJS.AES.encrypt(src,key,cfg)中传入的key都是字符串,所以导致了这样的情况。

如果只是随便用用倒没什么,但在前后端对接的时候这是一个大坑。

正确的做法是key和iv都使用CryptoJS.enc.Utf8.parse()CryptoJS.enc.Latin1.parse()去做一个转化,它传入到CryptoJS后进入的是另一块代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Encrypts a message.
*
* @param {Cipher} cipher The cipher algorithm to use.
* @param {WordArray|string} message The message to encrypt.
* @param {WordArray} key The key.
* @param {Object} cfg (Optional) The configuration options to use for this operation.
*
* @return {CipherParams} A cipher params object.
*
* @static
*
* @example
*
* var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key);
* var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv });
* var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL });
*/
encrypt: function (cipher, message, key, cfg) {
// Apply config defaults
cfg = this.cfg.extend(cfg);

// Encrypt
var encryptor = cipher.createEncryptor(key, cfg);
var ciphertext = encryptor.finalize(message);

// Shortcut
var cipherCfg = encryptor.cfg;

// Create and return serializable cipher params
return CipherParams.create({
ciphertext: ciphertext,
key: key,
iv: cipherCfg.iv,
algorithm: cipher,
mode: cipherCfg.mode,
padding: cipherCfg.padding,
blockSize: cipher.blockSize,
formatter: cfg.format
});
}

我们看到这一块代码中的key类型是一个WordArray,这才是真正意义上的key。

需要注意的是这个key和其他语言一样对长度是有要求的,如果你希望使用任意长度的key做加密,请用上面那种操作。