You need to enable JavaScript to run this app. ReactJS移植PHP端OpenSSL+zlib双层压缩AES加解密报错求助
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS移植PHP端OpenSSL+zlib双层压缩AES加解密报错求助

移植PHP OpenSSL加解密逻辑到ReactJS的问题定位与修复方案

原PHP端可正常运行的实现

$secret_key = 'thisIsK3y';
$secret_iv  = 'tHis1s1v';

$output         = false;
$encrypt_method = "AES-256-CBC";
$key            = hash( 'sha256', $secret_key );
$iv             = substr( hash( 'sha256', $secret_iv ), 0, 16 );
$action = 'e';
// 已知测试密文:33f6f1d3ebe5b9da5fd0e3a1c7dd71c6e484be914731cf96adac0c00 对应明文 "test"
$string = "33f6f1d3ebe5b9da5fd0e3a1c7dd71c6e484be914731cf96adac0c00";
if( $action == 'e' ) 
{
    $output = bin2hex(gzdeflate( gzdeflate(openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ), 9),  9) );
}
else if( $action == 'd' )
{
    if (!empty($string))
        $output = openssl_decrypt( gzinflate(gzinflate(pack("H*", $string ) ) ), $encrypt_method, $key, 0, $iv );
}
echo $output; // 输出test,PHP端运行无异常

移植阶段已准备的基础实现

  • 已引入依赖:CryptoJS、PakoJS
  • 已拆分的独立工具方法(存储于string.js)
export function gzdeflate(str) {
    return pako.deflateRaw(str);
}

export function gzinflate(str) {
    return pako.inflateRaw(str);
}

export function bin2hex(s) {
    let i
    let l
    let o = ''
    let n
    s += ''
    for (i = 0, l = s.length; i < l; i++) {
        n = s.charCodeAt(i)
        .toString(16)
        o += n.length < 2 ? '0' + n : n
    }
    return o
}

export function hex2bin(hex) {
    let bytes = [], str;

    for(var i=0; i< hex.length-1; i+=2){
        bytes.push(parseInt(hex.substr(i, 2), 16));
    }

    str = String.fromCharCode.apply(String, bytes);
    return str;
}

调试阶段实际出现的异常现象

  • hex2bin方法可正常执行无语法报错
  • 对十六进制密文执行两次gzinflate操作时,抛出Cannot read property 'ciphertext' of undefined错误
  • 调整secret_key、secret_iv参数重试后,抛出Error: invalid stored block lengths错误
  • 全流程跑通后输出结果始终无法和PHP端的明文对齐

核心问题定位与对应修复点

  1. 数据类型全链路不匹配
    • 原hex2bin方法返回JS字符串而非原始二进制字节流,Pako的inflateRaw方法要求输入为Uint8Array类型的压缩流字节,传入字符串时会因编码转换损坏原始字节结构,直接抛出非法压缩块长度的解压错误
    • Pako解压后返回的也是Uint8Array类型的二进制数据,不能直接转UTF8字符串传给CryptoJS,二进制字节转字符串的过程会因不可见字符截断、编码转换导致密文损坏
  2. 密钥长度与编码逻辑错位
    • PHP端的hash('sha256', $secret_key)输出为完整64位hex字符串,直接作为参数传入OpenSSL加密函数;原JS代码错误对SHA256结果做substr(0,32)截断,仅取前32位hex作为密钥,和PHP端实际使用的密钥字节完全不一致
    • PHP端的IV逻辑为截取SHA256结果的前16位hex字符,直接传入OpenSSL,JS端需要严格保持同样的截取规则,按字符串字节作为IV,长度刚好匹配AES-CBC要求的16字节块大小
  3. CryptoJS解密入参格式错误
    • 原代码给AES.decrypt第一个参数传入普通字符串,CryptoJS会默认按OpenSSL加盐格式(前缀为Salted__)解析密文,非标准格式的输入会被解析为ciphertext为undefined的对象,直接触发取值报错

修复后可直接运行的ReactJS端解密代码

// 引入依赖
import sha256 from 'crypto-js/sha256';
import CryptoJS from 'crypto-js';
import pako from 'pako';

// 替换原错误工具方法
function hexToUint8Array(hexStr) {
    const byteLen = hexStr.length / 2;
    const bytes = new Uint8Array(byteLen);
    for (let i = 0; i < byteLen; i++) {
        bytes[i] = parseInt(hexStr.substr(i * 2, 2), 16);
    }
    return bytes;
}

function uint8ToWordArray(u8arr) {
    return CryptoJS.lib.WordArray.create(u8arr);
}

let secret_key = "thisIsK3y";
let secret_iv = "tHis1s1v";
let action = 'd';
// 已知测试密文
let string = "33f6f1d3ebe5b9da5fd0e3a1c7dd71c6e484be914731cf96adac0c00";

let output = false;
// 对齐PHP端密钥逻辑:使用完整SHA256 hex字符串作为密钥,不做截断
const keyHex = sha256(secret_key).toString(CryptoJS.enc.Hex);
const key = CryptoJS.enc.Utf8.parse(keyHex);
// 对齐PHP端IV逻辑:截取SHA256结果前16位hex字符作为IV
const ivHex = sha256(secret_iv).toString(CryptoJS.enc.Hex).substr(0, 16);
const iv = CryptoJS.enc.Utf8.parse(ivHex);

if (action == 'd') {
    // 1. 十六进制密文转原始字节流
    const compressedLv1 = hexToUint8Array(string);
    // 2. 第一次解压
    const compressedLv2 = pako.inflateRaw(compressedLv1);
    // 3. 第二次解压拿到OpenSSL输出的原始密文
    const encryptedBuf = pako.inflateRaw(compressedLv2);
    // 4. 密文转CryptoJS可识别的WordArray格式
    const encryptedWord = uint8ToWordArray(encryptedBuf);
    // 5. 执行AES-256-CBC解密,显式传入ciphertext结构避免自动格式解析
    output = CryptoJS.AES.decrypt(
        { ciphertext: encryptedWord },
        key,
        { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
    ).toString(CryptoJS.enc.Utf8);
}
console.log(output); // 输出和PHP端一致的"test"

注意:PHP端两次连续gzdeflate是为了进一步压缩加密输出的二进制密文长度,移植时必须严格保持两次解压的顺序,不能省略步骤或颠倒解压顺序。


内容的提问来源于stack exchange,提问作者0x00b0

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.06 23:45:01