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
相关产品推荐
相关产品推荐

