下面是我的示例代码
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嵌入在加密数据中?如果是这样,使用或不使用salt并不重要,对吗?
2条答案
按热度按时间ecfsfe2w1#
据我所知,从输出中,你可以看到crypto-js在内部加密过程中自动从密码短语中导出具有随机盐和随机IV值的实际密钥,然后使用导出的密钥加密明文。
这只是部分正确。当密钥材料作为字符串传递时,CryptoJS确实使用了密钥派生函数。然而,在这种情况下,在加密期间仅生成 random 8字节盐,而不是IV。根据所使用的算法,使用密钥导出函数并使用密码和盐作为输入来沿着密钥导出IV然后应用导出的密钥和IV(默认使用CBC模式和PKCS#7填充)执行加密。
请注意,random salt对于安全性很重要,因为它为每个加密生成不同的密钥/IV对。密钥/IV对的重用将意味着或多或少的严重漏洞,具体取决于模式。
我的问题是,既然盐和IV是随机生成的,那么为什么解密函数可以用唯一的密钥密码来导出相同的AES密钥?
当使用密钥导出函数时,salt * 必须 * 以某种形式传递给解密端(但不是IV,因为它是导出的)。然后,在使用密钥导出函数解密期间,使用salt和密码来重构密钥和IV。最后,使用以这种方式导出的密钥和IV执行解密。
会不会是加密数据里的盐和IV?
salt可以通过不同的方式传递给
CryptoJS.AES.decrypt()
(如前所述,IV在派生时不会传递),例如封装在CipherParams
对象中( Package 密钥,iv,salt和密文)。一个
CipherParams
也是由CryptoJS.AES.encrypt()
生成的,所以返回值可以 * 直接 * 传递给CryptoJS.AES.decrypt()
。但是,如果CipherParams
对象只包含salt和密文,就足够解密了。或者,可以从
CipherParams
对象中提取密文和盐,并以任何所需的格式传递到解密端。一种特殊的格式是Base64编码的OpenSSL格式,它将
Salted__
的ASCII编码,salt和密文按此顺序连接在一起。Base64编码的OpenSSL格式的特点是它总是以U2FsdGVkX1
开头,因为前缀是常量。CryptoJS通过CipherParams
对象的toString()
函数直接支持这种格式。下面的脚本演示了这一点:
个字符
如果是的话,用不用盐都没关系,对吧?
这个问题我不是很清楚。如果你的意思是CryptoJS处理盐 * 隐式 *,那么是的(例如以OpenSSL格式)。如果你问在密钥派生过程中是否可以禁用盐:不,这在CryptoJS中是不可能的(不像OpenSSL)。当然,出于安全原因,也不建议禁用盐。
关于密钥派生函数的详细信息:
CryptoJS应用OpenSSL专有密钥派生函数
EVP_BytesToKey()
,迭代次数为1,MD5作为摘要。结合加密数据的OpenSSL格式,这实现了与OpenSSL的兼容性(只要MD5用作OpenSSL的摘要)。MD5是旧版本OpenSSL中的默认摘要。然而,从v1.1.0开始,OpenSSL切换到SHA-256作为默认摘要。因此,CryptoJS仅在OpenSSL语句中使用
-md
显式指定摘要为MD5时才与新版本的OpenSSL兼容。请注意:
EVP_BytesToKey()
现在被认为是不安全的,特别是因为迭代计数为1并且使用了损坏的摘要MD5。相反,对于新的实现,至少应该应用PBKDF 2(CryptoJS和OpenSSL都支持),或者,如果可用的话,Argon 2。请注意,如果密钥材料作为
WordArray
传递,则不执行密钥推导,但密钥材料 * 直接 * 用作密钥,s。然后,IV * 必须 * 显式指定为WordArray
(对于使用IV的所有模式)。以下脚本执行内置密钥推导 * 显式 *,并显示两个结果是等效的:
的字符串
请注意,CryptoJS的开发最近已经停止,库也不再维护。推荐的替代方案是WebCrypto或NodeJS的crypto模块。
vdgimpew2#
是的,对,但是我将这些问题归纳为两点,我希望你能解释一下
第一个,当加密密码时,CryptoJS生成一个随机盐和一个随机向量(IV)。这些用于导出加密密钥。然后将盐,IV和加密的消息组合成最终的密文。
在第二种情况下,当解密密码时,CryptoJS从密文中提取salt和IV,并将它们与密码一起沿着来导出原始加密密钥。然后使用该密钥将密文的其余部分解密为原始明文。