据我所知,从输出中可以看到,在内部加密过程中,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的加密模块.