如何使用 OpenSSL 解密 cCryptoGS 字符串?

问题描述 投票:0回答:1

使用 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 对其进行解密?

google-apps-script encryption openssl cryptojs
1个回答
0
投票

如果密钥材料作为字符串传递,它被解释为密码,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'

这次没有警告。

© www.soinside.com 2019 - 2024. All rights reserved.