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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 14:22:04