使用 cCryptoGS 库的 Google Apps 脚本中的示例加密:
function encrypt() {
let message = "Hello world!";
let pw = "password";
let encrypted = cCryptoGS.CryptoJS.AES.encrypt(message, pw).toString();
Logger.log(encrypted);
// Sample result, changes each time due to the salt
// U2FsdGVkX19A/TPmx/tmR9MRiKU9AQPhUYKD/lyoY/c=
};
此命令返回错误:
echo "U2FsdGVkX19A/TPmx/tmR9MRiKU9AQPhUYKD/lyoY/c=" | openssl enc -d -a -A -aes-256-cbc -iter 1 -md md5 -pass pass:'password' && echo
错误是:
bad decrypt
803B3E80A67F0000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:124:
OpenSSL 版本是
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
。如何使用 OpenSSL 对其进行解密?
如果密钥材料作为字符串传递,它被解释为密码,CryptoJS 执行 OpenSSL 兼容密钥派生。为此,CryptoJS 在底层实现了以 MD5 作为摘要的 OpenSSL 专有密钥派生函数
EVP_BytesToKey()
。因此,可以使用以下语句使用 OpenSSL 进行解密:
echo "U2FsdGVkX19A/TPmx/tmR9MRiKU9AQPhUYKD/lyoY/c=" | openssl enc -d -a -A -aes-256-cbc -md md5 -pass pass:'password'
请注意,OpenSSL 最初使用 MD5 作为默认摘要,但从 v1.1.0 开始,它应用 SHA-256。相比之下,CryptoJS 只使用 MD5。因此对于OpenSSL版本beforev1.1.0,-md md5选项可以省略
更安全的选择
评论中发布的 OpenSSL 语句包含 -iter 1 选项。此选项设置迭代计数并定义为密钥派生函数implicitly PBKDF2(因此它等同于-iter 1 -pbkdf2,参见here)。这就是解密失败的原因。
但是,PBKDF2 更安全。已弃用的密钥派生
EVP_BytesToKey()
被认为是不安全的,因为它使用损坏的摘要 MD5 和仅 1 的迭代计数,请参见 here。更现代的 OpenSSL CLI 版本相应地发出警告:
*** WARNING : deprecated key derivation used. Using -iter or -pbkdf2 would be better.
PBKDF2 另一方面,是一种可靠的密钥派生函数,它也受 CryptoJS 支持。因此,出于安全原因,应使用 PBKDF2。
不幸的是,对于内置的密钥派生,CryptoJS 不支持开箱即用地从一个密钥派生功能切换到另一个。内置的密钥推导内部调用了
OpenSSLKdf.execute()
,其中使用了EVP_BytesToKey()
。因此,必须实现应用 PBKDF2 的相应函数(以下称为OpenSSLPbkdf2
),例如:
var OpenSSLPbkdf2 = {
execute: function(password, keySize, ivSize, salt, hasher) {
if (!salt) {
salt = CryptoJS.lib.WordArray.random(8);
}
var key = CryptoJS.PBKDF2(password, salt, pbkdf2Params);
var iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);
key.sigBytes = keySize * 4;
return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
}
};
并且在
AES.encrypt()
/AES.decrypt()
中用{kdf: OpenSSLPbkdf2}
指定。 pbkdf2Params
设置 PBKDF2 参数(摘要、迭代计数和密钥大小)。迭代计数应尽可能高,同时保持可接受的性能。推荐的摘要是 SHA-256(从 v1.1.0 开始的 OpenSSL 默认摘要)。
例子:
var pbkdf2Params = {
hasher: CryptoJS.algo.SHA256,
iterations: 10000,
keySize: (256 + 128)/32,
};
var OpenSSLPbkdf2 = {
execute: function(password, keySize, ivSize, salt, hasher) {
if (!salt) {
salt = CryptoJS.lib.WordArray.random(8);
}
var key = CryptoJS.PBKDF2(password, salt, pbkdf2Params);
var iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);
key.sigBytes = keySize * 4;
return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
}
};
var password = 'password';
var plaintext = 'Hello world!';
// Encryption
var ciphertextParams = CryptoJS.AES.encrypt(plaintext, password, {kdf: OpenSSLPbkdf2});
console.log(ciphertextParams.toString());
// Decryption (ciphertext in OpenSSL format)
var dec = CryptoJS.AES.decrypt(ciphertextParams.toString(), password, {kdf: OpenSSLPbkdf2});
console.log(dec.toString(CryptoJS.enc.Utf8));
// Decryption (ciphertext as CipherParams object)
var dec = CryptoJS.AES.decrypt(ciphertextParams, password, {kdf: OpenSSLPbkdf2});
console.log(dec.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
这种方式生成的数据可以用下面的OpenSSL语句解密:
echo "U2FsdGVkX1+K5JPuhBTtQdvUzcfvu2rbFNuBq2QTDzI=" | openssl enc -d -a -A -aes-256-cbc -iter 10000 -md sha256 -pass pass:'password'
这次没有警告。