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

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),验证失败
?>

问题原因分析

  1. 签名数据源不一致:PHP代码是对原始base64编码的JSON数据签名,而Node.js代码错误地对加密后的数据签名,两者签名的目标数据完全不同,导致验证失败。
  2. JSON序列化差异:PHP的json_encode和Node.js的JSON.stringify默认输出可能存在差异(如属性顺序、空格),会导致base64编码后的数据不一致。
  3. Base64编码兼容性:Node.js中的btoa函数无法处理Unicode字符,而PHP的base64_encode基于字节流,应该用Buffer处理以保证一致性。
  4. 时间戳格式差异: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:10:35