我偶然发现了一个问题,你传递给CryptoJS.AES.encrypt
函数的IV值似乎被忽略了。
import CryptoJS from "crypto-js";
const text = "This is secret text!";
const key = "abc";
const iv = CryptoJS.lib.WordArray.random(2);
const encryptedText = CryptoJS.AES.encrypt(text, key, { iv }).toString();
console.log(encryptedText);
const decryptedText = CryptoJS.AES.decrypt(encryptedText, key, { iv: 'whatever' }).toString(CryptoJS.enc.Utf8);
console.log(decryptedText);
生成以下输出:
U2FsdGVkX1//3KxGM2FnVV4qR5KmWBxP/xyI1+YITvdQeXqrUBH7spIvi/Ny+7S2
This is secret text!
因此,不管您传递给函数的是什么IV值,函数似乎都能够解密文本。
我到底做错了什么?
2条答案
按热度按时间3htmauhk1#
CryptoJS使用的是AES-CBC。CBC不是一种认证的操作模式,因此它不提供消息完整性/真实性。CBC只提供机密性,而且只有在正确使用时才提供机密性,并且不允许例如填充oracle攻击。
实际上,CBC模式的IV在解密期间仅改变明文的第一块,并且只有那些IV不同的字节。所以所有其余的块如你所期望的那样简单地解密。这是因为IV仅仅是 * 初始化向量 *。在AES块加密之前与明文异或的其他“向量”是先前的密文块。也许最好看看解释操作模式的维基百科页面。
请注意,行:
不正确:
iv
应为命名字段:从这个问题的其他上下文来看,这是否是故意的还不完全清楚。
eit6fx6z2#
在加密和解密时,可以在代码中指定 * any * IV,因为IV根本不使用。为什么这样做?
根据密钥材料的类型,CryptoJS使用一个密钥(如果传递了WordArray)或密码(如果传递了字符串)。您传递了字符串,因此密钥材料被解释为密码(这就是为什么
key
最好被称为password
)。在这种情况下,CryptoJS使用密钥派生函数(EVP_BytesToKey()
)来导出密钥和IV。* 显式 * 传递的IV被完全忽略。这适用于加密和解密。因此,如果应用密码,不应该使用IV(并且应该删除代码中的IV)。派生密钥和IV可以从
CryptoJS.AES.encrypt()
返回的CipherParams
对象中选取(s. here):这里,密钥可以从属性key
读取,IV可以从属性iv
读取,密文可以从属性ciphertext
读取。当您传递密钥而不是密码时,您还必须传递IV(否则会显示错误消息)。如果您在解密过程中指定了错误的IV,这将导致CBC(默认模式)的第一个块损坏。other answer中描述了详细信息。
如果您想检查这一点,您应该使用Latin1解码(或十六进制编码)而不是UTF-8解码来解码解密数据,因为损坏的数据通常不兼容UTF-8(并且CryptoJS将显示错误消息)。
请注意,短明文和只有一个块大小的密文通常会破坏填充,这反过来又会在取消填充时破坏数据,使数据无法显示。但是,可以通过禁用默认的PKCS#7填充来显示 * padded * 数据:
padding: CryptoJS.pad.NoPadding
)。以下代码显示了使用密码(1)、密钥和IV(2)进行加密/解密,以及使用错误的IV解密时CBC的第一个块损坏(3):
完整性:在您的代码中,
CipherParams
对象将使用toString()
转换为Base64编码的OpenSSL格式。OpenSSL格式包括Salted__
的ASCII编码,后跟8字节salt和实际的密文。由于常量前缀Salted__
,Base64编码的数据始终以U2FsdGVkX1
开头(与您的密文类似)。随机盐是在加密过程中生成的。基于盐和密码,使用密钥导出函数来导出密钥和IV。为了能够在解密过程中重构密钥和IV,需要盐。盐不是秘密的。因此它通常与密文连接(与上述OpenSSL格式相同)。salt还包含在
CipherParams
对象的salt
属性中。如果只使用密钥而不使用密码,则没有salt,
toString()
只返回ciphertext
属性的Base64编码。CryptoJS.AES.decrypt()
需要一个CipherParams
对象(在此处为s.)。或者(如代码中所示)可以传递使用toString()
创建的Base64编码。这将隐式转换为CipherParams
对象。