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

PHP转Node.js:3DES零填充加密结果不一致问题求助

PHP转Node.js:3DES零填充加密结果不匹配问题

我正在把一段PHP代码迁移到Node.js,需要实现DES-EDE3(3DES)算法的零填充加密,但始终无法复现PHP的加密结果:

  • PHP每次运行生成固定的tcParametros字符串,最初Node.js每次结果不同;
  • 修复后tcFirma哈希已和PHP匹配,但tcParametros仍有细微差异(仅16字符不同),且无法被PHP客户端API解密。

原PHP代码

$TokenService = "SECRET_TOKEN";
$Email = "generic email";
$Telefono = "generic phone number";
$PedidoID = "666";
$Monto = "120";
$Moneda = "2";
$P1 = "callback url (for testing purposes it can be just google.com)";
$P2 = "success url (for testing purposes it can be just google.com)";
$P3 = "        [{\"Serial\":1,\"Producto\":\"PRODUCTO1\",\"LinkPago\":0,\"Cantidad\":2,\"Precio\":10,\"Descuento\":    0,\"Total\":20},{\"Serial\":2,\"Producto\":\"PRODUCTO2\",\"LinkPago\":0,\"Cantidad\":5,\"Precio\":20,\"Descuento\":0,\"Total\":100}]";
$P4 = "11\u0000\u0000\u0000";
$signature_string = "$TokenService|" . $Email . "|" . $Telefono . "|" . $PedidoID . "|" . $Monto . "|" . $Moneda . "|" . $P1 . "|" . $P2 . "|" . $P3 . "|" . $P4;

$signature = encrypt_password($signature_string);
echo "tcFirma: " . $signature . PHP_EOL;

function encrypt_password($password)
{
    // Usa la función hash de PHP con el algoritmo SHA-256
    $password_encriptada = hash('sha256', $password);
    return $password_encrypted; // 注:此处存在变量名笔误,实际运行需修正为$password_encriptada
}

// tcParameters
$tcFirma = $signature;
$TokenSecret = "ANOTHER_SECRET_TOKEN";
$datos_de_pago = $tcFirma . "|" . $Email . "|" . $Telefono . "|" . $PedidoID . "|" . $Monto . "|" . $Moneda . "|" . $P1 . "|" . $P2 . "|" . $P3 . "|" . $P4;

$lnSizeDatosPago = strlen($datos_de_pago);
$datos_de_pago = str_pad($datos_de_pago, ($lnSizeDatosPago + 8 - ($lnSizeDatosPago % 8)), "\0");

$tcParametros = openssl_encrypt(
    $datos_de_pago,
    'DES-EDE3',
    $TokenSecret,
    OPENSSL_ZERO_PADDING
);
$tcParametros = base64_encode($tcParametros);

echo "tcParameters: " . $tcParametros . PHP_EOL;

优化后的Node.js代码(原版本)

const sha256 = require('sha256');
const crypto = require('crypto');

// TC firma
const TokenService = 'SECRET_TOKEN';
const Email = "generic email";
const Telefono = "generic phone number";
const PedidoID = "666";
const Monto = "120";
const Moneda = "2";
const P1 = "callback url (for testing purposes it can be just google.com)";
const P2 = "success url (for testing purposes it can be just google.com)";
const P3 = '[{"Serial":1,"Producto":"PRODUCTO1","LinkPago":0,"Cantidad":2,"Precio":10,"Descuento":0,"Total":20},{"Serial":2,"Producto":"PRODUCTO2","LinkPago":0,"Cantidad":5,"Precio":20,"Descuento":0,"Total":100}]';

const P4 = '11\\u0000\\u0000\\u0000';

const signature_string =
TokenService +
'|' +
Email +
'|' +
Telefono +
'|' +
PedidoID +
'|' +
Monto +
'|' +
Moneda +
'|' +
P1 +
'|' +
P2 +
'|' +
P3 +
'|' +
P4;
const signature = encriptar_password(signature_string);

console.log('tcFirma: ' + signature + '\n');

function encriptar_password(password) {
    // Use the SHA-256 algorithm to hash the password
    const password_encriptada = sha256(password);
    return password_encriptada;
}

// tcParameters
const tcFirma = signature;
const TokenSecret = 'ANOTHER_SECRET_TOKEN';

let datos_de_pago = `${tcFirma}|${Email}|${Telefono}|${PedidoID}|${Monto}|${Moneda}|${P1}|${P2}|${P3}|${P4}`;

let lnSizeDatosPago = datos_de_pago.length;

datos_de_pago = datos_de_pago.padEnd(lnSizeDatosPago + 8 - (lnSizeDatosPago % 8), '\0');

const cipher = crypto.createCipheriv('des-ede3', TokenSecret, null);
let encrypted = cipher.update(datos_de_pago, null, 'base64'); // 1st Base64 encoding
encrypted += cipher.final('base64');

console.log('tcParametros: ' + btoa(encrypted) + '\n');

问题排查与修复方案

1. 核心差异点

(1)P4的空字节解析错误

PHP中$P4 = "11\u0000\u0000\u0000"会把\u0000解析为ASCII空字节,但Node.js原代码里的'11\\u0000\\u0000\\u0000'是把\u0000当作普通字符串处理,导致原始拼接内容完全不一致。

修复:Node.js中直接使用空字节表示:

const P4 = '11' + '\x00\x00\x00';

(2)重复Base64编码

PHP中openssl_encrypt输出原始二进制,再做一次Base64编码;但Node.js原代码先把加密结果转成Base64,又用btoa二次编码,导致结果长度翻倍、内容完全不符。

修复:去掉多余的btoa,直接输出第一次Base64编码结果:

console.log('tcParametros: ' + encrypted + '\n');

(3)加密时的编码指定

Node.js的cipher.update需明确指定输入编码为utf8,避免默认编码差异:

let encrypted = cipher.update(datos_de_pago, 'utf8', 'base64');

2. 修复后的完整Node.js代码

const sha256 = require('sha256');
const crypto = require('crypto');

// TC firma
const TokenService = 'SECRET_TOKEN';
const Email = "generic email";
const Telefono = "generic phone number";
const PedidoID = "666";
const Monto = "120";
const Moneda = "2";
const P1 = "callback url (for testing purposes it can be just google.com)";
const P2 = "success url (for testing purposes it can be just google.com)";
const P3 = '[{"Serial":1,"Producto":"PRODUCTO1","LinkPago":0,"Cantidad":2,"Precio":10,"Descuento":0,"Total":20},{"Serial":2,"Producto":"PRODUCTO2","LinkPago":0,"Cantidad":5,"Precio":20,"Descuento":0,"Total":100}]';
// 修复:直接使用ASCII空字节
const P4 = '11' + '\x00\x00\x00';

const signature_string = 
  TokenService + '|' + Email + '|' + Telefono + '|' + PedidoID + '|' + 
  Monto + '|' + Moneda + '|' + P1 + '|' + P2 + '|' + P3 + '|' + P4;

const signature = encriptar_password(signature_string);
console.log('tcFirma: ' + signature + '\n');

function encriptar_password(password) {
  return sha256(password);
}

// tcParameters
const tcFirma = signature;
const TokenSecret = 'ANOTHER_SECRET_TOKEN';

let datos_de_pago = `${tcFirma}|${Email}|${Telefono}|${PedidoID}|${Monto}|${Moneda}|${P1}|${P2}|${P3}|${P4}`;
let lnSizeDatosPago = datos_de_pago.length;

// 零填充:补空字节到8的倍数
datos_de_pago = datos_de_pago.padEnd(lnSizeDatosPago + 8 - (lnSizeDatosPago % 8), '\0');

// 3DES加密:与PHP参数对齐,无需IV
const cipher = crypto.createCipheriv('des-ede3', TokenSecret, null);
// 指定输入编码,输出直接用Base64
let encrypted = cipher.update(datos_de_pago, 'utf8', 'base64');
encrypted += cipher.final('base64');

console.log('tcParametros: ' + encrypted + '\n');

3. 验证步骤

  • 对比PHP和Node.js的signature_string十六进制值,确保内容完全一致;
  • 确认填充后的datos_de_pago长度是8的倍数;
  • 直接对比最终tcParametros字符串,应与PHP输出完全匹配。

内容的提问来源于stack exchange,提问作者Amir Velasquez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:37:01