Java客户端ECDSA签名后,Node.js服务端远程提取(r,s)并验证签名
Java ECDSA签名与Node.js服务端验证实现方案
我来帮你搞定Java客户端签名、Node.js服务端验证的全流程,咱们一步步拆解:
一、Java端签名代码补全
你给出的签名逻辑已经是正确的,我帮你补全完整可运行的代码,避免编码和参数遗漏:
import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.Signature; import java.util.Base64; public class ECDSASigner { // 明确指定UTF-8编码,避免环境默认编码差异 private static final String UTF_8 = StandardCharsets.UTF_8.name(); public static byte[] sign(String plainText, PrivateKey privateKey) throws Exception { // 实例化SHA1withECDSA签名对象 Signature ecdsaSigner = Signature.getInstance("SHA1withECDSA"); // 用私钥初始化签名器 ecdsaSigner.initSign(privateKey); // 更新要签名的明文(统一UTF-8编码) ecdsaSigner.update(plainText.getBytes(UTF_8)); // 生成ASN.1 DER格式的签名字节数组 return ecdsaSigner.sign(); } // 可选:把签名转成Base64方便网络传输 public static String signToBase64(String plainText, PrivateKey privateKey) throws Exception { byte[] signatureBytes = sign(plainText, privateKey); return Base64.getEncoder().encodeToString(signatureBytes); } }
二、Node.js端签名验证实现
服务端拿到公钥的x、y坐标后,需要先构造合法的EC公钥,再验证Java生成的签名。这里最关键的是曲线一致性(Java私钥的曲线必须和Node.js用的曲线匹配),具体代码如下:
const crypto = require('crypto'); /** * 验证ECDSA签名 * @param {string} plainText - 签名对应的明文 * @param {Buffer} signatureBuffer - Java生成的签名字节数组(或Base64解码后的Buffer) * @param {string} xHex - 公钥x坐标的十六进制字符串(无0x前缀) * @param {string} yHex - 公钥y坐标的十六进制字符串(无0x前缀) * @param {string} curveName - EC曲线名称,默认secp256r1(NIST P-256) * @returns {boolean} 验证结果:true=有效,false=无效 */ function verifyECDSASignature(plainText, signatureBuffer, xHex, yHex, curveName = 'secp256r1') { try { // 1. 用x、y坐标构造ECDH对象,生成标准SPKI格式的公钥 const ecDH = crypto.createECDH(curveName); ecDH.setPublicKey(Buffer.from(xHex, 'hex'), Buffer.from(yHex, 'hex')); const publicKey = ecDH.getPublicKey('spki'); // 2. 创建验证器,算法要和Java端对应:Java的SHA1withECDSA对应Node.js的'ecdsa-with-SHA1' const verifier = crypto.createVerify('ecdsa-with-SHA1'); // 3. 更新明文(和Java端一致用UTF-8编码) verifier.update(plainText, 'utf8'); // 4. 验证签名:Java的签名是ASN.1 DER格式,Node.js的verify可以直接识别 return verifier.verify(publicKey, signatureBuffer); } catch (error) { console.error('签名验证出错:', error); return false; } } // 示例用法 const plainText = "需要签名的明文内容"; // 假设从Java端拿到的Base64签名 const signatureBase64 = "Java生成的签名Base64字符串"; const signatureBuffer = Buffer.from(signatureBase64, 'base64'); // 公钥的x、y十六进制坐标 const publicKeyX = "公钥x的十六进制字符串"; const publicKeyY = "公钥y的十六进制字符串"; // 执行验证 const isValid = verifyECDSASignature(plainText, signatureBuffer, publicKeyX, publicKeyY); console.log('签名是否有效:', isValid);
三、踩坑必看的关键注意事项
- 曲线必须严格匹配:Java端私钥使用的EC曲线要和Node.js的
curveName参数完全一致。你可以在Java端打印曲线名称确认:
常见曲线名称:import java.security.interfaces.ECPrivateKey; import java.security.spec.ECParameterSpec; ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey; ECParameterSpec params = ecPrivateKey.getParams(); System.out.println("当前EC曲线名称:" + params.getCurve().getName());secp256r1、secp384r1、secp256k1等。 - 编码绝对统一:明文必须全程用UTF-8编码,Java端的
getBytes(UTF_8)对应Node.js的'utf8'参数。 - 公钥坐标格式正确:x、y坐标必须是无
0x前缀的十六进制字符串,长度要符合曲线要求(比如secp256r1的x、y都是32字节,对应64位十六进制字符)。
内容的提问来源于stack exchange,提问作者Sudheesh Singanamalla
相关产品推荐
相关产品推荐

