You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP与Node.js crypto的AES-256-CTR加解密不一致问题求助

解决PHP与Node.js AES-256-CTR加解密不兼容的问题

你遇到的核心问题是Node.js旧版加密API与PHP默认加密逻辑的密钥、IV生成规则不匹配,导致两边的加密参数不一致,最终密文无法兼容。

问题根源拆解

  1. Node.js端:createCipher/createDecipher使用的是EVP_BytesToKey算法从密码派生密钥和IV,默认用MD5哈希计算,生成的IV并非全零值。
  2. PHP端:你调用openssl_encrypt时未指定IV参数,CTR模式下会默认使用全零IV;同时PHP不会自动用EVP_BytesToKey处理密码,直接将短密码补零到32字节当密钥(AES-256要求32字节密钥),这和Node.js的密钥派生结果完全不同。

兼容方案:统一加密参数逻辑

下面提供两种解决方案,推荐第二种(符合现代加密安全规范):

方式1:模拟Node.js旧API的EVP_BytesToKey逻辑(兼容现有Node.js输出)

先实现PHP版的EVP_BytesToKey,生成和Node.js完全一致的密钥和IV:

function evpBytesToKey($password, $salt = null, $keyLen = 32, $ivLen = 16) {
    $key = '';
    $iv = '';
    $data = '';
    while (strlen($key) < $keyLen || strlen($iv) < $ivLen) {
        $data .= $password . $salt;
        $md5Hash = md5($data, true);
        $data = $md5Hash;
        $key .= substr($md5Hash, 0, min(strlen($md5Hash), $keyLen - strlen($key)));
        if (strlen($iv) < $ivLen) {
            $iv .= substr($md5Hash, strlen($key), min(strlen($md5Hash) - strlen($key), $ivLen - strlen($iv)));
        }
    }
    return ['key' => $key, 'iv' => $iv];
}

// 测试代码
$password = 'd6F3Efeq';
$text = 'pereira';
// Node.js的createCipher默认无盐,所以salt传null
$params = evpBytesToKey($password, null, 32, 16);
$encrypted = bin2hex(openssl_encrypt($text, 'aes-256-ctr', $params['key'], OPENSSL_RAW_DATA, $params['iv']));
echo $encrypted; // 现在输出会和Node.js的148bc695286379一致

对应Node.js代码(如果需要继续使用旧逻辑):

const crypto = require('crypto');
const algorithm = 'aes-256-ctr';
const password = 'd6F3Efeq';

function encrypt(text){
  const cipher = crypto.createCipher(algorithm,password);
  let crypted = cipher.update(text,'utf8','hex');
  crypted += cipher.final('hex');
  return crypted;
}

console.log(encrypt('pereira')); // 输出148bc695286379

方式2:使用现代加密规范(推荐)

废弃不安全的createCipher,改用createCipheriv/createDecipheriv,手动生成随机IV并随密文传输,用PBKDF2派生密钥:

Node.js代码
const crypto = require('crypto');
const algorithm = 'aes-256-ctr';
const password = 'd6F3Efeq';

function encrypt(text) {
  const salt = crypto.randomBytes(16); // 随机盐
  const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256'); // 派生32字节密钥
  const iv = crypto.randomBytes(16); // CTR模式IV推荐16字节(和块大小一致)
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
  // 返回盐+IV+密文的hex(也可用base64)
  return salt.toString('hex') + iv.toString('hex') + encrypted.toString('hex');
}

function decrypt(encryptedHex) {
  const salt = Buffer.from(encryptedHex.slice(0, 32), 'hex'); // 前32位是盐(16字节)
  const iv = Buffer.from(encryptedHex.slice(32, 64), 'hex'); // 中间32位是IV(16字节)
  const encrypted = Buffer.from(encryptedHex.slice(64), 'hex');
  const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
  const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
  return decrypted.toString('utf8');
}

// 测试
const encrypted = encrypt('pereira');
console.log('加密结果:', encrypted);
console.log('解密结果:', decrypt(encrypted));
PHP代码
function encrypt($text, $password) {
    $salt = random_bytes(16);
    $key = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true);
    $iv = random_bytes(16);
    $encrypted = openssl_encrypt($text, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv);
    // 拼接盐、IV、密文并转hex
    return bin2hex($salt) . bin2hex($iv) . bin2hex($encrypted);
}

function decrypt($encryptedHex, $password) {
    $salt = hex2bin(substr($encryptedHex, 0, 32));
    $iv = hex2bin(substr($encryptedHex, 32, 32));
    $encrypted = hex2bin(substr($encryptedHex, 64));
    $key = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true);
    return openssl_decrypt($encrypted, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv);
}

// 测试
$password = 'd6F3Efeq';
$text = 'pereira';
$encrypted = encrypt($text, $password);
echo '加密结果: ' . $encrypted . PHP_EOL;
echo '解密结果: ' . decrypt($encrypted, $password) . PHP_EOL;

关键注意事项

  • AES-256必须使用32字节(256位)密钥,短密码需通过统一的密钥派生算法生成,避免两边补位逻辑不一致。
  • CTR模式要求IV唯一且不可预测,重复使用IV会导致加密安全漏洞,推荐随机生成并随密文传输。
  • Node.js的createCipher/createDecipher已被官方标记为废弃,建议尽快升级到createCipheriv/createDecipheriv。

内容的提问来源于stack exchange,提问作者Esteban M.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:18:47