hkt1998

hkt1998

为什么AES在线加密每次结果不一样(CryptoJS库)

2023-08-23

某次在使用AES在线加密网站的时候遇到了两个问题:

  1. 相同明文和密钥的情况下,每次加密结果不一致,但都可以正常解密出相同的明文。

  2. 密钥长度无需指定,甚至用空密钥也可以。

示例网站:在线AES加密 | AES解密 - 在线工具 (sojson.com)

密文内容会变,base64 编码,开头一段总是固定的字符。

虽然每次加密结果不一致,但开头的一段数据总是U2FsdGVkX1,于是先解base64查看有没有可读的内容。

密文总是以Salted__开头,看来是加了盐,密文中应该包含了盐的信息。

查看网站代码,看样子是使用了一个叫做CryptoJS的第三方库。

npm上能找到crypto-js,可阅读代码。

crypto-js - npm (npmjs.com)

cryptojs-npm查看源码,文件cipher-core.js第646行左右,parse函数的作用是解析出实际密文和salt值。以word(一个word是8个16进制,就是32个bit,4个字节)为单位将原密文分割为数组,ciphertextWords[0]0x53616c74(Salt),ciphertextWords[1]0x65645f5f(ed__),ciphertextWords[2]ciphertextWords[3]就是盐值,剩下的就是实际的密文。

parse: function (openSSLStr) {
		var salt;

	    // 先解 base64 编码
	    var ciphertext = Base64.parse(openSSLStr);

	    // 以 word 为单位分割成数组
	    var ciphertextWords = ciphertext.words;

	    // 判断开始是否是 Salted__
	    if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) {
	        // 解析出盐值
	        salt = WordArray.create(ciphertextWords.slice(2, 4));

	        // 去掉开头和盐值,剩下的就是实际密文
	        ciphertextWords.splice(0, 4); //删除"salted__"+salt
	        ciphertext.sigBytes -= 16;
	    }

	    return CipherParams.create({ ciphertext: ciphertext, salt: salt });
	}

十六进制转字符串

进一步阅读源码可知,盐值是随机生成,不需要指定盐值。同时有一个密钥派生函数,根据输入的字符串派生出符合长度要求的密钥,所以即使用户输入的密钥长度不满足条件,也是可以正常加密的。