Node.js生成RSA签名后在PHP中验证失败的问题求助
Node.js生成RSA签名后PHP验证失败的解决方案
问题背景
原有一套纯PHP实现的RSA签名验证流程,密钥生成、签名、验证全在PHP中执行时完全有效。现需要改用Node.js生成签名,再用PHP验证,但验证始终失败。
原PHP工作代码
<?php // 创建新的RSA密钥对 $new_key_pair = openssl_pkey_new(array( "private_key_bits" => 2048, "private_key_type" => OPENSSL_KEYTYPE_RSA, )); openssl_pkey_export($new_key_pair, $private_key_pem); file_put_contents('private_key.pem', $private_key_pem); // 保存私钥到文件 /** * 前端签名流程 */ $obj = (object) [ 'name' => 'alex', 'age' => 27, 'time' => time() // 每次生成不同签名 ]; // 对JSON对象做base64编码 $data = base64_encode(json_encode($obj)); // 加载私钥 $pem_private_key = file_get_contents('private_key.pem'); $private_key = openssl_pkey_get_private($pem_private_key); // 加密数据(仅演示,签名不依赖加密后的数据) openssl_private_encrypt($data, $crypted, $pem_private_key, OPENSSL_PKCS1_PADDING); // 对原始base64编码的data生成签名 openssl_sign($data, $signature, $private_key, OPENSSL_ALGO_SHA256); /** * 后端验证流程 */ // 获取公钥 $pem_public_key = openssl_pkey_get_details($private_key)['key']; // 验证签名 $result = openssl_verify($data, $signature, $pem_public_key, "sha256WithRSAEncryption"); if ($result == 1) { echo "DATA: " . $data . "\n"; echo "SIGNATURE: " . base64_encode($signature) . "\n"; // 解密数据 openssl_public_decrypt($crypted, $decrypted, $pem_public_key, OPENSSL_PKCS1_PADDING); echo "Data decryption: " . base64_decode($decrypted) . "\n"; } elseif ($result == 0) { echo "signature is invalid for given data.\n"; } else { echo "error: " . openssl_error_string() . "\n"; } ?>
原Node.js尝试代码(验证失败)
const fs = require("fs"); const crypto = require("crypto"); const signer = crypto.createSign("RSA-SHA256"); // 私钥加密函数 function encryptString(data, privateKey) { const encrypted = crypto.privateEncrypt(privateKey, data); return encrypted.toString("base64"); } let obj = { name: "alex", age: 27, time: Date.now(), }; // 使用btoa做base64编码(存在兼容性问题) let data = btoa(JSON.stringify(obj)); // 加载私钥 let privateKey = fs.readFileSync("./private_key.pem"); // 加密数据 let encoded = encryptString(data, privateKey); // 错误:对加密后的数据签名,而非原始base64的data signer.update(encoded); signature_b64 = signer.sign(privateKey, "base64"); console.log("Signature:", signature_b64); console.log("Encrypted Data:", encoded); console.log("Original Base64 Data:", data);
原PHP验证代码
<?php $pem_private_key = file_get_contents('private_key.pem'); $private_key = openssl_pkey_get_private($pem_private_key); $pem_public_key = openssl_pkey_get_details($private_key)['key']; // Node.js生成的签名 $signature = base64_decode("MAgFdpMSHYRKIQjbfeqlLwHgostJN0YrkeBrNwapk6qYsNaSt7M+cA2y+rzQNOw35D3nxrDP5ICx8mVErMwu5uptkhFyaxay+DbBufHvfQvnXhq/bAZR9I7PAfS23cnNBnEmchATY/exVCk2tB82GattHbXIJxEcC2Z8OwI4+1hdAg31PbC7fjjeXxKh9ZGYtZVfpjhFzH2ybcsPqLnYJRoWTxg6CfO+/Mp6f+SI5KKqB5tYxqoHtdcL4Tw2xK3NQPmqoUoKAynO3hzp/jMyI0QnSnnpgJJOHcJB9llrvBolShvgqL8It0uVCNFD0Qj8Vaix0LRLOSMxftirQTR/4Q=="); // Node.js生成的加密后数据(而非原始签名用的data) $data = "jh9RSS+UpnsHXeQMES5Y2MS2WA0hoHCOHVI/l4LIj0u9AVa/WGPpGiD18rRZ3i2oP+mmVGXmhVW1VFjplsRGE9rxR+fnoI24kOzjMBIF3fOG4OrsZNLBDZ3K1y14mcssG3JVMO4mvFP5UaJMVKmQVwT8WgqeUkaADz4ayYZ+fporSYxDOT+HHHNJJhM+vMqh66MDC9yHPeAhYtoq+FvdMP1cTGoKHStUznxnmmQiDRnz4jvrvo+/EVA73sCeZWv+duu0kOGPMP23LfbKa3JAiNdtqC1LI1vz3GIwuRgnOKz1cRh5mypdR3VrM51gIvapbTHKjvtrmRZUUsAcFhIdQg=="; // 验证签名 $result = openssl_verify($data, $signature, $pem_public_key, "sha256WithRSAEncryption"); var_dump($result); // 输出int(0),验证失败 ?>
问题原因分析
- 签名数据源不一致:PHP代码是对原始base64编码的JSON数据签名,而Node.js代码错误地对加密后的数据签名,两者签名的目标数据完全不同,导致验证失败。
- JSON序列化差异:PHP的
json_encode和Node.js的JSON.stringify默认输出可能存在差异(如属性顺序、空格),会导致base64编码后的数据不一致。 - Base64编码兼容性:Node.js中的
btoa函数无法处理Unicode字符,而PHP的base64_encode基于字节流,应该用Buffer处理以保证一致性。 - 时间戳格式差异:PHP的
time()返回秒级时间戳,Node.js的Date.now()返回毫秒级,导致JSON数据不一致。
修正后的Node.js代码
const fs = require("fs"); const crypto = require("crypto"); // 确保JSON序列化和PHP一致:排序键、无空格 function jsonStringify(obj) { return JSON.stringify(obj, Object.keys(obj).sort()); } let obj = { name: "alex", age: 27, time: Math.floor(Date.now() / 1000), // 和PHP的time()保持一致(秒级时间戳) }; // 用Buffer做base64编码,和PHP的base64_encode一致 const jsonStr = jsonStringify(obj); const data = Buffer.from(jsonStr).toString("base64"); // 加载私钥 const privateKey = fs.readFileSync("./private_key.pem", "utf8"); // 初始化签名器,使用标准算法标识 const signer = crypto.createSign("SHA256withRSAEncryption"); // 对原始base64编码的data签名(和PHP保持一致) signer.update(data); // 生成base64格式的签名 const signatureB64 = signer.sign(privateKey, "base64"); // 加密数据(可选,仅和原PHP流程对齐,不影响签名) const encrypted = crypto.privateEncrypt(privateKey, Buffer.from(data)); const encryptedB64 = encrypted.toString("base64"); console.log("Original Base64 Data:", data); console.log("Signature (Base64):", signatureB64); console.log("Encrypted Data (Base64):", encryptedB64);
修正后的PHP验证代码
<?php $pem_private_key = file_get_contents('private_key.pem'); $private_key = openssl_pkey_get_private($pem_private_key); $pem_public_key = openssl_pkey_get_details($private_key)['key']; // Node.js生成的原始base64数据 $data = "eyJhZ2UiOjI3LCJuYW1lIjoiYWxleCIsInRpbWUiOjE3MTk2NzQwMTN9"; // Node.js生成的签名(替换为实际输出的signatureB64) $signature = base64_decode("替换为Node.js输出的签名字符串"); // 验证签名 $result = openssl_verify($data, $signature, $pem_public_key, OPENSSL_ALGO_SHA256); if ($result == 1) { echo "签名验证通过\n"; // 可选:解密数据 $encrypted = base64_decode("替换为Node.js输出的encryptedB64"); openssl_public_decrypt($encrypted, $decrypted, $pem_public_key, OPENSSL_PKCS1_PADDING); echo "解密后数据:" . base64_decode($decrypted) . "\n"; } else { echo "签名验证失败\n"; if ($result == -1) { echo "错误信息:" . openssl_error_string() . "\n"; } } ?>
验证结果
修正后的代码可以实现Node.js生成签名,PHP成功验证,完全兼容原PHP流程。
内容的提问来源于stack exchange,提问作者desh
相关产品推荐
相关产品推荐

