使用 ECB 模式的 CryptoJS AES 加密使用相同的参数会产生不同的结果

2024-04-02

正如本文中提到的answer https://stackoverflow.com/a/47096284/940030,我可以使用 ECB 模式将转换后的值反转回纯文本,而不仅仅是将其与另一个哈希值进行比较。

但是,使用以下代码片段:

const x = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()
const y = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()

console.log(x, y, x === y)
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

I get:

U2FsdGVkX19blKXDRXfdgXyviCrZtouB0cPcJPoR/cQ= U2FsdGVkX1+1AwWqKWntLVkh7DtiZxPDYCDNsjmc8LM= false

难道我做错了什么?有没有办法达到预期的效果?


首先:对于相同的明文和相同的密钥,总是会生成相同的密文ECB https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_(ECB) mode!

If a WordArray用作第二个参数,则CryptoJS.AES.encrypt执行一个用密钥加密得到的密文是完全相同的正如预期的那样(here https://cryptojs.gitbook.io/docs/#the-cipher-input):

function encryptWithKey(plaintext, key){
    var encrypted = CryptoJS.AES.encrypt(plaintext, key, { mode: CryptoJS.mode.ECB });
    console.log("Ciphertext (Base64):\n" + encrypted.toString());        // Ciphertext
    var decrypted = CryptoJS.AES.decrypt(encrypted.toString(), key, { mode: CryptoJS.mode.ECB });
    console.log("Decrypted:\n" + decrypted.toString(CryptoJS.enc.Utf8)); // Plaintext
}

var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
encryptWithKey('abc', key);
encryptWithKey('abc', key);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

但如果使用字符串作为第二个参数,CryptoJS.AES.encrypt执行一个使用密码加密得到的密文是不同的 (here https://cryptojs.gitbook.io/docs/#the-cipher-input)。尽管如此,解密当然会返回原始明文:

function encryptWithPassphrase(plaintext, passphrase){
    var encrypted = CryptoJS.AES.encrypt(plaintext, passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Ciphertext (OpenSSL):\n" + encrypted.toString());       // Salt and actual ciphertext in OpenSSL format
    var decrypted = CryptoJS.AES.decrypt(encrypted.toString(), passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Decrypted:\n" + decrypted.toString(CryptoJS.enc.Utf8)); // Plaintext
}

encryptWithPassphrase('abc', '123'); 
encryptWithPassphrase('abc', '123');
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

解释:
在使用密码进行加密期间random生成 8 字节盐,并与密码一起生成实际密钥(32 字节,AES-256)。
盐的目的是使彩虹表的使用变得不可行。由于盐每次都是随机生成的,因此生成的密钥不同,密文也不同。
CryptoJS.AES.encrypt返回一个CipherParams https://cryptojs.gitbook.io/docs/#the-cipher-output封装了相关参数(如盐和实际密文)的对象。
toString()将此对象转换为 OpenSSL 格式,该格式由 ASCII 编码组成Salted__,后面是 8 字节 salt,后面是实际的密文,全部一起进行 Base64 编码。因此,所有密文都以U2FsdGVkX1.

function encryptWithPassphraseParams(plaintext, passphrase){
    var encrypted = CryptoJS.AES.encrypt(plaintext, passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Salt (hex):\n" + encrypted.salt);                 // Salt (hex)
    console.log("Key (hex):\n" + encrypted.key);                   // Key (hex)
    console.log("Ciphertext (hex):\n" + encrypted.ciphertext);     // Actual ciphertext (hex)
    console.log("Ciphertext (OpenSSL):\n" + encrypted.toString()); // Salt and actual ciphertext, Base64 encoded, in OpenSSL format
    console.log("\n");
}

encryptWithPassphraseParams('abc', '123'); 
encryptWithPassphraseParams('abc', '123');
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

Details:
CryptoJS 使用 OpenSSL 功能EVB_BytesToKey https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html派生密钥时使用摘要 MD5 和迭代次数为 1,这不是很安全。更安全的是使用可靠的 KDF(例如 PBKDF2)并使用生成的密钥进行后续加密。
除了安全之外,还需要注意的是EVB_BytesToKey没有实现标准,因此必须首先在不可用的库中实现(或从互联网复制)此功能。

Note:ECB 是一种不安全的模式,不应使用(here https://crypto.stackexchange.com/a/20946),更好的是像 GCM 这样的认证加密。有关 CryptoJS 的更多详细信息可以在其文档中找到(here https://cryptojs.gitbook.io/docs/).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 ECB 模式的 CryptoJS AES 加密使用相同的参数会产生不同的结果 的相关文章

随机推荐