以下是我的示例代码

const CryptoJS = require('crypto-js')

const plaintext = "hello world"
const passphrase = "my_passphrase"

const encrypted = CryptoJS.AES.encrypt(plaintext, passphrase)
console.log("plaintext =", plaintext)
console.log("passphrase =", passphrase)
console.log("-----------------------------------------------------------")
console.log("key =", encrypted.key+'')
console.log("iv =", encrypted.iv+'')
console.log("salt =", encrypted.salt+'')
console.log("encrypted =", encrypted+'')

console.log("-----------------------------------------------------------")

var decrypted = CryptoJS.AES.decrypt(encrypted.toString(), passphrase);
console.log("decrypted =", decrypted.toString(CryptoJS.enc.Utf8))

输出结果如下:

plaintext = hello world
passphrase = my_passphrase
-----------------------------------------------------------
key = 7bb8ed7c0c9ad5b714a57073068f441dfbf032173e60bf61deea2f9a5ea2ad3a
iv = 72b8e7e60fbcf1328fd1994ea2cc7f06
salt = 03a04d2d438b4cac
encrypted = U2FsdGVkX18DoE0tQ4tMrKBbK/veZm1k0vGmFxl6sow=
-----------------------------------------------------------
decrypted = hello world

据我所知,从输出中可以看到,crypto-js在内部加密过程中自动从口令中派生出带有随机盐和随机IV值的实际密钥,然后使用派生的密钥对明文进行加密.

我的问题是,既然SALT和IV是随机生成的,那么为什么解密函数只使用密钥密码就可以得出相同的AES密钥?会不会是嵌入在加密数据中的SALT和IV?如果是这样,用不用盐都无关紧要,对吧?

推荐答案

据我所知,从输出中可以看到,在内部加密过程中,crypto-js自动从口令中获取带有随机盐和随机IV值的实际密钥,然后使用派生的密钥对明文进行加密.

This is only partly correct. CryptoJS does indeed use a key derivation function when the key material is passed as string. In this case, however, only a random 8 bytes salt is generated during encryption and not an IV. The IV is derived along with the key using a key derivation function and with the password and salt as input according to the algorithm used (AES-256 by default). Encryption is then performed applying the derived key and IV (with CBC mode and PKCS#7 padding by default).
Note that a random salt is important for security because it generates different key/IV pairs for each encryption. The reuse of key/IV pairs would mean a more or less serious vulnerability depending on the mode.

我的问题是,既然SALT和IV是随机生成的,那么为什么解密函数只使用密钥密码就可以得出相同的AES密钥?

它不能.当使用密钥导出函数时,盐must以某种形式被传递到解密方(但不是IV,因为它被导出).然后,在使用密钥派生函数的解密过程中,使用盐和口令来重建密钥和IV.最后,用这种方法得到的密钥和IV进行解密.

会不会是嵌入在加密数据中的SALT和IV?

The salt can be passed to CryptoJS.AES.decrypt() in different ways (as mentioned, the IV is not passed as it is derived), for instance encapsulated in a CipherParams object (wrapping key, iv, salt and ciphertext).
A CipherParams is also generated by CryptoJS.AES.encrypt(), so the return value can be passed directly to CryptoJS.AES.decrypt(). However, it is sufficient for decryption if the CipherParams object only contains salt and ciphertext.
Alternatively, the ciphertext and salt can be extracted from the CipherParams object and passed to the decrypting side in any desired format.
A special format is the Base64 encoded OpenSSL format, which concatenates the ASCII encoding of Salted__, the salt and the ciphertext in this order. Characteristic for the Base64 encoded OpenSSL format is that it always starts with U2FsdGVkX1 because of the constant prefix. CryptoJS supports this format directly with the toString() function of the CipherParams object.

以下脚本演示了这一点:

var password = "test passphrase"
var plaintext = "The quick brown fox jumps over the lazy dog"

// test 1: pass CipherParams object directly from encrypt() to decrypt()
var dataCP = CryptoJS.AES.encrypt(plaintext, password)
console.log("test 1:", CryptoJS.AES.decrypt(dataCP, password).toString(CryptoJS.enc.Utf8))

// test 2: pass a fresh CipherParams object to decrypt() that contains only salt and ciphertext
var dataCP_onlySaltAndCiphertext = CryptoJS.lib.CipherParams.create({ salt: dataCP.salt, ciphertext: dataCP.ciphertext })
console.log("test 2:", CryptoJS.AES.decrypt(dataCP_onlySaltAndCiphertext, password).toString(CryptoJS.enc.Utf8))
console.log("       ", CryptoJS.AES.decrypt({ salt: dataCP.salt, ciphertext: dataCP.ciphertext }, password).toString(CryptoJS.enc.Utf8)) // or more compact

// test 3: pass data to decrypt() in Base64 encoded OpenSSL format using toString()
var dataOpenSSL = dataCP.toString()
console.log("test 3:", CryptoJS.AES.decrypt(dataOpenSSL, password).toString(CryptoJS.enc.Utf8))

// test 4: pass data to decrypt() in explicitly generated Base64 encoded OpenSSL format 
var dataOpenSSL_explicit = CryptoJS.enc.Utf8.parse("Salted__").concat(dataCP.salt).concat(dataCP.ciphertext).toString(CryptoJS.enc.Base64)
console.log("test 4:", CryptoJS.AES.decrypt(dataOpenSSL_explicit, password).toString(CryptoJS.enc.Utf8))
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>

如果是这样,用不用盐都无关紧要,对吧?

这个问题我不太清楚.如果您的意思是CryptoJS处理SALT implicitly,那么是的(例如,在OpenSSL格式中).如果您询问在密钥派生过程中是否可以禁用SALT:不可以,这在CryptoJS中是不可能的(与OpenSSL不同).当然,出于安全原因,也不建议禁用盐.


密钥派生函数详情:

CryptoJS applies the OpenSSL proprietary key derivation function EVP_BytesToKey() with an iteration count of 1 and MD5 as digest. In conjunction with the OpenSSL format with regard to the encrypted data, this achieves compatibility with OpenSSL (as long as MD5 is used as digest for OpenSSL).
MD5 was the default digest in older OpenSSL versions. From v1.1.0, however, OpenSSL switched to SHA-256 as default digest. Hence, CryptoJS is only compatible with newer OpenSSL versions if the digest is explicitly specified as MD5 in the OpenSSL statement using -md.
Attention: EVP_BytesToKey() is considered insecure nowadays, especially because of the iteration count of 1 and the use of the broken digest MD5. Instead, for new implementations at least PBKDF2 should be applied (supported by both CryptoJS and OpenSSL) or, if available, Argon2.

注意,如果密钥material 被作为WordArray传递,则不执行密钥推导,但是密钥material 被用作密钥directly,S.然后,IV must被显式地指定为WordArray(对于使用1的所有模式).

以下脚本执行内置密钥派生explicitly,并显示两个结果相等:

// 1. Encrypt with built-in key derivation
var password = "test passphrase"
var plaintext = "The quick brown fox jumps over the lazy dog"
var encryptedCP = CryptoJS.AES.encrypt(plaintext, password) // keymaterial is a string => encryption with key derivation
var saltWA = encryptedCP.salt
console.log("salt", saltWA.toString())
console.log("ciphertext, OpenSSL format, built-in", encryptedCP.toString())

// 2. Encrypt with explicit key derivation
// - Generate 32 bytes key key and 16 bytes IV using EVP_BytesToKey using password and salt from above
var keySize = 8; // key size for AES-256: 8 words (a 4 bytes) = 32 bytes
var ivSize = 4;  // iv size for AES: 4 words (a 4 bytes) = 16 bytes
var keyIvWA = CryptoJS.EvpKDF(password, saltWA, {keySize: keySize + ivSize, iterations: 1, hasher: CryptoJS.algo.MD5})
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, keySize), keySize * 4)
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(keySize), ivSize * 4)
// - Encrypt with AES-256 in CBC mode (default) and PKCS#7 padding (default) without built-in key derivation
var encryptedCP = CryptoJS.AES.encrypt(plaintext, keyWA, {iv: ivWA}) // keymaterial is a WordArray => encryption without key derivation
var encryptedCPOpenSSL = CryptoJS.enc.Utf8.parse("Salted__").concat(saltWA).concat(encryptedCP.ciphertext).toString(CryptoJS.enc.Base64)
console.log("ciphertext, OpenSSL format, explicit", encryptedCPOpenSSL)

// 3. Decrypt
var decrypted = CryptoJS.AES.decrypt(encryptedCPOpenSSL, password)
console.log(decrypted.toString(CryptoJS.enc.Utf8))
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>


请注意,CryptoJS的开发版本是最近的discontinued,不再维护该库.推荐 Select WebCrypto或NodeJS的加密模块.

Node.js相关问答推荐

即使DDB键不存在, node Lambda也不会失败,并返回NULL作为结果

如何在不丢失其他键的情况下解开子文档数组,然后反转该过程?

如何在.npmrc中添加 comments ?

Mongoose抱怨说,整数是数字,而不是整数

验证器功能在mongoose 中不起作用

找不到 vue-template-compiler@2.6.14 的匹配版本 | Shopware 6.5 更新后的 node 问题

使用pm2启动服务器

node_modules/preact/src/jsx.d.ts:2145:22 - 错误 TS2304:找不到名称SVGSetElement

module.exports=require('other') 和临时变量有什么区别?

图像存储在后端文件夹中,但使用 multer 和 react.js 在前端找不到

如何在 NestJS 中使用外部生成的 swagger.json?

即使部署成功,也不会触发 Firebase 函数来填充 Firestore 集合.为什么?

baseurl64 缓冲区解码

如何在 MongoDB collection.find() 上获取回调

Nodejs-console.error vs util.debug

带有加密的nodejs中的SALT和HASH密码

Node.js + Express:应用程序不会开始监听端口 80

有人在 NodeJS 中实现过 wiki 吗?

如何在express 中设置默认路径(路由前缀)?

如何从 Node.js 应用程序Ping?