Python生成ECDSA数字签名,Node.js验证失败问题求助
Python ECDSA-p521签名无法在Node.js中验证的问题
问题背景
使用Python Cryptography库进行ECDSA-p521数字签名,Node.js Crypto库完成验证,两者单独运行时签名/验证功能均正常,但Python生成的签名传入Node.js验证时返回False。
密钥对(来自Hashicorp Vault,类型:ecdsa-p521)
PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIB1ZsDtu9EeMUma3p0M4jWrA4jJrBirDyhLfATvXVMWNDmrH5EX49T\ndOzAsKq92IXi2g1HtdIaYBBTde5lkBNfUK+gBwYFK4EEACOhgYkDgYYABAESgmpD\nCcElrFK/HPwJaNUkSly25hcQe0KnSFoIIlIBVba5jPxz2erUDIPr34RINUb3LS7j\n5MxELHl/VINKFdNhzgGJT4IWzL9VR6p8auN4+DNy7lLr4veBvB3yY87MkfaRWqlB\nyjpMre0vudtOAHUbZ6F6l4BPBPIAQaYHHiuNWSrjlg==\n-----END EC PRIVATE KEY-----" PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBEoJqQwnBJaxSvxz8CWjVJEpctuYXEHtCp0haCCJSAVW2uYz8c9nq1AyD69+ESDVG9y0u4+TMRCx5f1SDShXTYc4BiU+CFsy/VUeqfGrjePgzcu5S6+L3gbwd8mPOzJH2kVqpQco6TK3tL7nbTgB1G2ehepeATwTyAEGmBx4rjVkq45Y=\n-----END PUBLIC KEY-----"
Python签名代码(已修复格式兼容问题)
import base64 from os import urandom from cryptography.hazmat.primitives import hashes, utils from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature PRIVATE_KEY = "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIB1ZsDtu9EeMUma3p0M4jWrA4jJrBirDyhLfATvXVMWNDmrH5EX49T\ndOzAsKq92IXi2g1HtdIaYBBTde5lkBNfUK+gBwYFK4EEACOhgYkDgYYABAESgmpD\nCcElrFK/HPwJaNUkSly25hcQe0KnSFoIIlIBVba5jPxz2erUDIPr34RINUb3LS7j\n5MxELHl/VINKFdNhzgGJT4IWzL9VR6p8auN4+DNy7lLr4veBvB3yY87MkfaRWqlB\nyjpMre0vudtOAHUbZ6F6l4BPBPIAQaYHHiuNWSrjlg==\n-----END EC PRIVATE KEY-----" PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBEoJqQwnBJaxSvxz8CWjVJEpctuYXEHtCp0haCCJSAVW2uYz8c9nq1AyD69+ESDVG9y0u4+TMRCx5f1SDShXTYc4BiU+CFsy/VUeqfGrjePgzcu5S6+L3gbwd8mPOzJH2kVqpQco6TK3tL7nbTgB1G2ehepeATwTyAEGmBx4rjVkq45Y=\n-----END PUBLIC KEY-----" def sign() -> dict[str, str]: privKey = load_pem_private_key( PRIVATE_KEY.encode(), password=None, backend=default_backend, ) msg = urandom(12) chosen_hash = hashes.SHA256() hasher = hashes.Hash(chosen_hash, backend=default_backend) hasher.update(msg) digest = hasher.finalize() signature = privKey.sign( digest, ec.ECDSA(utils.Prehashed(chosen_hash)), ) # 关键修改:将原始签名转换为Node.js兼容的ASN.1 DER格式 r, s = utils.decode_dss_signature(signature) asn1_signature = utils.encode_dss_signature(r, s) return { "msg": base64.b64encode(msg).decode(), "sign": base64.b64encode(asn1_signature).decode(), } def verify(signObj: dict[str, str]) -> bool: pubKey = load_pem_public_key( PUBLIC_KEY.encode(), backend=default_backend, ) try: pubKey.verify( base64.b64decode(signObj["sign"].encode()), base64.b64decode(signObj["msg"].encode()), ec.ECDSA(hashes.SHA256()), ) except InvalidSignature: return False return True if __name__ == "__main__": res = sign() print(res) print(verify(res))
Node.js/TypeScript验证代码
import * as crypto from "crypto"; const algo = "sha256"; const base64 = "base64"; const PRIVATE_KEY = "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIB1ZsDtu9EeMUma3p0M4jWrA4jJrBirDyhLfATvXVMWNDmrH5EX49T\ndOzAsKq92IXi2g1HtdIaYBBTde5lkBNfUK+gBwYFK4EEACOhgYkDgYYABAESgmpD\nCcElrFK/HPwJaNUkSly25hcQe0KnSFoIIlIBVba5jPxz2erUDIPr34RINUb3LS7j\n5MxELHl/VINKFdNhzgGJT4IWzL9VR6p8auN4+DNy7lLr4veBvB3yY87MkfaRWqlB\nyjpMre0vudtOAHUbZ6F6l4BPBPIAQaYHHiuNWSrjlg==\n-----END EC PRIVATE KEY-----"; const PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBEoJqQwnBJaxSvxz8CWjVJEpctuYXEHtCp0haCCJSAVW2uYz8c9nq1AyD69+ESDVG9y0u4+TMRCx5f1SDShXTYc4BiU+CFsy/VUeqfGrjePgzcu5S6+L3gbwd8mPOzJH2kVqpQco6TK3tL7nbTgB1G2ehepeATwTyAEGmBx4rjVkq45Y=\n-----END PUBLIC KEY-----"; class SignObj { readonly msg: string; readonly sign: string; constructor(msg: string, sign: string) { this.msg = msg; this.sign = sign; } } function sign(): SignObj { const msg = crypto.randomBytes(12); const hash = crypto.createHash(algo).update(msg).end(); const hashDigest = hash.digest(); const signer = crypto.createSign(algo); signer.write(hashDigest); signer.end(); const sign = signer.sign(PRIVATE_KEY, base64); return new SignObj(msg.toString(base64), sign); } function verify(signObj: SignObj): boolean { const hash = crypto .createHash(algo) .update(Buffer.from(signObj.msg, base64)) .end(); const hashDigest = hash.digest(); const verifier = crypto.createVerify(algo); verifier.write(hashDigest); verifier.end(); return verifier.verify(PUBLIC_KEY, Buffer.from(signObj.sign, base64)); } // 测试Python生成的签名 const testRes = new SignObj( "Oz4ZRvzcTRs7CEKV", "MIGGAkEkbIqN8uPwzMf50NiqXKuSdT4c+8Lchz7o5eKPfiPp3jMDtW1TqQseCJw7/8mIcvTRdYeFlYsf58vFbxkk/jpi/QJBBkw9MKelCXcEPecM5/5HqJrkD2XijtNgj7AKzsMTv/Y7721S6hnb48sBKj6DTQ9Dfnsf1uPZNb4KIa7KhwX5ebw=" ); console.log(verify(testRes)); // 现在会返回True
问题原因及解决说明
问题核心是签名格式不兼容:
- Python Cryptography库默认生成raw格式ECDSA签名(r和s两个大整数的字节直接拼接,p521曲线下总长度132字节)。
- Node.js Crypto库的
verify方法默认期望ASN.1 DER编码的签名格式(包含r和s的结构化编码)。
修复方案通过Python的utils.decode_dss_signature解析原始签名得到r和s,再用utils.encode_dss_signature转换为ASN.1 DER格式,生成的签名即可直接在Node.js中通过验证。
内容的提问来源于stack exchange,提问作者Strife8240
相关产品推荐
相关产品推荐

