将 PHP open_ssl_decrypt AES 256 CBC 实现为 CryptoJS

如何解决将 PHP open_ssl_decrypt AES 256 CBC 实现为 CryptoJS

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

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

    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,$iv_size);

        if (substr($salt_header,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,$iv_size)
    {
        $m = "";
        while (strlen($m) < 48) {
            $m .= hashCryptoDigestSHA256Sum($m,$key,$salt);
        }
        $result = array(
            'password' => substr($m,32),'iv' => substr($m,32,$iv_size)
        );
        return $result;
    }

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

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

$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

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 中调用最后一个函数:

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

这显示为空: 数据解密:

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

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

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

var password = 'Nootric2703202'

有了这个:

var password = 'mypassword'

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

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,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,{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

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,"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,"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,&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,&options,&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,method,password,iv,iv_len,aad_len))) {
        RETVAL_STR(ret);
    } else {
        RETVAL_FALSE;
    }
}

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

/* {{{ 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",&raw_output) == FAILURE) {
        RETURN_THROWS();
    }
    mdtype = EVP_get_digestbyname(method);
    if (!mdtype) {
        php_error_docref(NULL,"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 密钥派生兼容。

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

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

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 表达式解密:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-