将 PHP open_ssl_decrypt AES 256 CBC 实现为 CryptoJS [英] Implement Php open_ssl_decrypt AES 256 CBC as CryptoJS

查看:40
本文介绍了将 PHP open_ssl_decrypt AES 256 CBC 实现为 CryptoJS的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试在 ReactJs(不是 NodeJs)中编写以下代码,但这在 JS 中不起作用.

I try to make the following code in ReactJs (not NodeJs) but this doesn't work in JS.

PHP 中的原始代码工作正常:

The original code in PHP works fine:

    function decryptOpensslDigestSHA256Sum($data)
    {
        $key = hash('sha256', 'Nootric2703202'); //My password has 14 characters

        $method = 'AES-256-CBC';

        $data = base64_decode($data);

        $iv_size = openssl_cipher_iv_length($method);

        $salt_header = substr($data, 0, $iv_size);

        if (substr($salt_header, 0, 8) != "Salted__") {
            return "";
        }
        $salt = substr($salt_header, 8);

        $creds = extractOpenSSLCreds($key, $salt, $iv_size);

        $data = openssl_decrypt(substr($data, $iv_size), $method, $creds['password'], OPENSSL_RAW_DATA, $creds['iv']);

        return $data;
    }

    function extractOpenSSLCreds($key, $salt, $iv_size)
    {
        $m = "";
        while (strlen($m) < 48) {
            $m .= hashCryptoDigestSHA256Sum($m, $key, $salt);
        }
        $result = array(
            'password' => substr($m, 0, 32),
            'iv' => substr($m, 32, $iv_size)
        );
        return $result;
    }

    function hashCryptoDigestSHA256Sum($hash, $key, $salt)
    {
        $hash.= $key.$salt;
        $prev = openssl_digest($hash, "sha256", true);
        return $prev;
    }

如果我在 php 中调用这个函数:

If I invoke in php this function:

$data = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
echo "Data urlEncoded: $data<br>";
$decryption = decryptOpensslDigestSHA256Sum($data);
echo "Data decrypted: $decryption<br><br>";

这表明:数据解密:email=abc@xyz.abc&name=&gpw_id=gpwID

但是当我尝试在 JS 中使用此功能时,这不起作用(我在此 Stackoverflow 条目中找到的源代码 decrypt-openssl-aes-256-cbc-in-browser-cryptojs

But when I try to use in JS this function this doesn't work (the source code I found on this Stackoverflow entry decrypt-openssl-aes-256-cbc-in-browser-cryptojs

function CryptoJSAesDecrypt(encrypted){
    // 1. Separate ciphertext and salt
    var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
    var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix

    var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4));  // 8 bytes salt: 0x0123456789ABCDEF
    var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext        

    // 2. Determine key and IV using PBKDF2
    var password = 'Nootric2703202'
    var keyIvWA = CryptoJS.PBKDF2(
    password, 
    saltWA, 
    {
        keySize: (32+16)/4,          // key and IV
        iterations: 10000,
        hasher: CryptoJS.algo.SHA256
    }
    );
    var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
    var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));

    // 3. Decrypt
    var decryptedWA = CryptoJS.AES.decrypt(
    {ciphertext: ciphertextWA}, 
    keyWA, 
    {iv: ivWA}
    );
    var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
    return decrypted;
}

如果我在 JS 中调用最后一个函数:

If I invoke the last function in JS:

    const dec = CryptoJSAesDecrypt("U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==");
    console.log("Data Decrypted: " + dec);

这显示为空:数据解密:

如果我使用这些数据进行调用:

If I invoke with this data:

    const dec = CryptoJSAesDecrypt("U2FsdGVkX18BI0VniavN78vlhR6fryIan0VvUrdIr+YeLkDYhO2xyA+/oVXJj/c35swVVkCqHPh9VdRbNQG6NQ==");
    console.log("Data Decrypted: " + dec);

在 Javascript 函数中,我替换了这一行:

And in the Javascript function I replace this line:

var password = 'Nootric2703202'

有了这个:

var password = 'mypassword'

这很好用!但是用我自己的密码 &我的加密数据这个JS解密代码不起作用.请帮忙?

This works fine! But with my own password & my encrypted data this JS decrypt code doesn't work. Some help please?

const dec = CryptoJSAesDecrypt();
console.log("Decrypted: " + dec);

function CryptoJSAesDecrypt(encrypted){
    // 1. Separate ciphertext and salt
    var encrypted = "U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==";
    console.log("Encrypted:", encrypted);

    var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
    var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4)); // Salted__ prefix

    var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4)); // 8 bytes salt: 0x0123456789ABCDEF 

    var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext 

    // 2. Determine key and IV using PBKDF2
    var password = 'Nootric2703202'
    var keyIvWA = CryptoJS.PBKDF2(
    password, 
    saltWA, 
    {
        keySize: (32+16)/4,          // key and IV
        iterations: 10000,
        hasher: CryptoJS.algo.SHA256
    }
    );
    var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
    var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));

    // 3. Decrypt
    var decryptedWA = CryptoJS.AES.decrypt(
    {ciphertext: ciphertextWA}, 
    keyWA, 
    {iv: ivWA}
    );
    var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
    return decrypted;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

这里是 PHP openssl-decrypt 的源代码(它是一个 openssl.c 文件)PHP openssl.c:

Here is the source code of PHP openssl-decrypt (It's an openssl.c file) PHP openssl.c:

PHP_OPENSSL_API zend_string* php_openssl_decrypt(
    const char *data, size_t data_len,
    const char *method, size_t method_len,
    const char *password, size_t password_len,
    zend_long options,
    const char *iv, size_t iv_len,
    const char *tag, zend_long tag_len,
    const char *aad, size_t aad_len)
{
    const EVP_CIPHER *cipher_type;
    EVP_CIPHER_CTX *cipher_ctx;
    struct php_openssl_cipher_mode mode;
    int i = 0, outlen;
    zend_string *base64_str = NULL;
    bool free_iv = 0, free_password = 0;
    zend_string *outbuf = NULL;

    PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(data_len, data);
    PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(password_len, password);
    PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(aad_len, aad);
    PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(tag_len, tag);


    cipher_type = EVP_get_cipherbyname(method);
    if (!cipher_type) {
        php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
        return NULL;
    }

    cipher_ctx = EVP_CIPHER_CTX_new();
    if (!cipher_ctx) {
        php_error_docref(NULL, E_WARNING, "Failed to create cipher context");
        return NULL;
    }

    php_openssl_load_cipher_mode(&mode, cipher_type);

    if (!(options & OPENSSL_RAW_DATA)) {
        base64_str = php_base64_decode((unsigned char*)data, data_len);
        if (!base64_str) {
            php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input");
            EVP_CIPHER_CTX_free(cipher_ctx);
            return NULL;
        }
        data_len = ZSTR_LEN(base64_str);
        data = ZSTR_VAL(base64_str);
    }

    if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
                &password, &password_len, &free_password,
                &iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE ||
            php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
                data, data_len, aad, aad_len, 0) == FAILURE) {
        outbuf = NULL;
    } else if (mode.is_single_run_aead ||
            EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) {
        outlen += i;
        ZSTR_VAL(outbuf)[outlen] = '\0';
        ZSTR_LEN(outbuf) = outlen;
    } else {
        php_openssl_store_errors();
        zend_string_release_ex(outbuf, 0);
        outbuf = NULL;
    }

    if (free_password) {
        efree((void *) password);
    }
    if (free_iv) {
        efree((void *) iv);
    }
    if (base64_str) {
        zend_string_release_ex(base64_str, 0);
    }
    EVP_CIPHER_CTX_reset(cipher_ctx);
    EVP_CIPHER_CTX_free(cipher_ctx);
    return outbuf;
}

/* {{{ Takes raw or base64 encoded string and decrypts it using given method and key */
PHP_FUNCTION(openssl_decrypt)
{
    zend_long options = 0;
    char *data, *method, *password, *iv = "", *tag = NULL, *aad = "";
    size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0;
    zend_string *ret;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsss", &data, &data_len, &method, &method_len,
                    &password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) {
        RETURN_THROWS();
    }

    if (!method_len) {
        zend_argument_value_error(2, "cannot be empty");
        RETURN_THROWS();
    }

    if ((ret = php_openssl_decrypt(data, data_len, method, method_len, password, password_len, options, iv, iv_len, tag, tag_len, aad, aad_len))) {
        RETVAL_STR(ret);
    } else {
        RETVAL_FALSE;
    }
}

PHP openssl-digest 的源代码是这样的:

And the source code for PHP openssl-digest is this:

/* {{{ Computes digest hash value for given data using given method, returns raw or binhex encoded string */
PHP_FUNCTION(openssl_digest)
{
    bool raw_output = 0;
    char *data, *method;
    size_t data_len, method_len;
    const EVP_MD *mdtype;
    EVP_MD_CTX *md_ctx;
    unsigned int siglen;
    zend_string *sigbuf;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &data, &data_len, &method, &method_len, &raw_output) == FAILURE) {
        RETURN_THROWS();
    }
    mdtype = EVP_get_digestbyname(method);
    if (!mdtype) {
        php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
        RETURN_FALSE;
    }

    siglen = EVP_MD_size(mdtype);
    sigbuf = zend_string_alloc(siglen, 0);

    md_ctx = EVP_MD_CTX_create();
    if (EVP_DigestInit(md_ctx, mdtype) &&
            EVP_DigestUpdate(md_ctx, (unsigned char *)data, data_len) &&
            EVP_DigestFinal (md_ctx, (unsigned char *)ZSTR_VAL(sigbuf), &siglen)) {
        if (raw_output) {
            ZSTR_VAL(sigbuf)[siglen] = '\0';
            ZSTR_LEN(sigbuf) = siglen;
            RETVAL_STR(sigbuf);
        } else {
            int digest_str_len = siglen * 2;
            zend_string *digest_str = zend_string_alloc(digest_str_len, 0);

            make_digest_ex(ZSTR_VAL(digest_str), (unsigned char*)ZSTR_VAL(sigbuf), siglen);
            ZSTR_VAL(digest_str)[digest_str_len] = '\0';
            zend_string_release_ex(sigbuf, 0);
            RETVAL_NEW_STR(digest_str);
        }
    } else {
        php_openssl_store_errors();
        zend_string_release_ex(sigbuf, 0);
        RETVAL_FALSE;
    }

    EVP_MD_CTX_destroy(md_ctx);
}

推荐答案

PHP 实现使用 EVP_BytesToKey() 作为密钥派生函数,因此与 CryptoJS 密钥派生兼容.

The PHP implementation uses EVP_BytesToKey() as key derivation function and is thus compatible with the CryptoJS key derivation.

但是,CryptoJS 默认使用 MD5 作为摘要,而 PHP 代码使用 SHA256(请注意,OpenSSL 从 v1.1.0 版本开始将默认摘要从 MD5 更改为 SHA256).
此外,用于密钥派生函数的密码不是密码本身(即 Nootric2703202),而是密码的十六进制编码 SHA256 哈希值.

However, CryptoJS applies MD5 as digest by default, while the PHP code uses SHA256 (note that OpenSSL has changed the default digest from MD5 to SHA256 as of version v1.1.0).
Moreover, the password applied for the key derivation function is not the password itself (i.e. Nootric2703202) but the hex encoded SHA256 hash of the password.

如果考虑到这一点,使用 CryptoJS 解密是:

If this is taken into account, decryption with CryptoJS is:

var password = 'Nootric2703202';
var passwordHashWA = CryptoJS.SHA256(password);
var passwordHashHex = passwordHashWA.toString(CryptoJS.enc.Hex); 

var ciphertext = 'U2FsdGVkX1++7PN6CsF5Bi38t0N3EjXpH5oGpaIZXUwk4T8QCwcATjvA4b/8VaxD8nf/MZhKPnWb1L8raLR4lw==';

CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();           
var data = CryptoJS.AES.decrypt(ciphertext, passwordHashHex);
console.log(data.toString(CryptoJS.enc.Utf8));

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

产生预期的明文:

email=abc@xyz.abc&name=&gpw_id=gpwID

由于 OpenSSL 的兼容性,密文也可以用以下 OpenSSL 表达式解密:

Due to OpenSSL compatibility, the ciphertext can also be decrypted with the following OpenSSL expression:

openssl enc -aes-256-cbc -d -md sha256 -in <ciphertextFile> -k d0f95d5e54a7aa25934a5d4915c9e2a06dadac20d16551693be1d21d4d8e8798 -A -a -p

其中 是包含 Base64 编码密文(无换行符)的文件的路径:U2FsdGVkX1...,以及密码 d0f95d... 是密码 Nootric2703202 的十六进制编码 SHA256 哈希值.

where <ciphertextFile> is the path to a file containing the Base64 encoded ciphertext (without linebreaks): U2FsdGVkX1..., and the password d0f95d... is the hex encoded SHA256 hash of the password Nootric2703202.

请记住,EVP_BytesToKey() 被认为是不安全的,s.例如此处.相反,应该使用像 PBKDF2 这样可靠的密钥派生函数.

Please keep in mind that EVP_BytesToKey() is considered insecure, s. e.g. here. Instead, a reliable key derivation function like PBKDF2 should be used.

这篇关于将 PHP open_ssl_decrypt AES 256 CBC 实现为 CryptoJS的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆