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

JS生成的P-256 ECDSA签名在Python验证失败,报MalformedPointError

问题分析与解决

问题描述

前端通过JavaScript Web Crypto API生成P-256(secp256r1)密钥对并签名交易,后端用Python的ecdsa库验证时抛出ecdsa.errors.MalformedPointError: Point does not lay on the curve错误,更换cryptography库验证同样失败。

前端密钥生成代码

// Generate P-256 key pair
const keyPair = await window.crypto.subtle.generateKey(
    {
        name: 'ECDSA',
        namedCurve: 'P-256'
    },
    true,
    ['sign']
);

前端签名代码

let transaction = {
    sender: address,
    recipient: recipient,
    amount: amount
};

// Convert the transaction data to a JSON string
let dataString = JSON.stringify(transaction);

// Calculate the hash of the transaction data
let data_hash = arrayBufferToHex(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(dataString)));

// Sign the hash
const signature = await window.crypto.subtle.sign(
    {
        name: 'ECDSA',
        hash: { name: 'SHA-256' }
    },
    keyPair,
    hexToArrayBuffer(data_hash)
);

// Convert the signature to hex
const signatureHex = arrayBufferToHex(signature);

// Add the signature and original data hash to the transaction
transaction.signature = signatureHex;
transaction.hash = data_hash;

后端原验证代码(错误版本)

from ecdsa import VerifyingKey, SECP256k1
from binascii import unhexlify

def verify(public_key, signature, data_hash):
    public_key_bytes = unhexlify(public_key)
    signature_bytes = unhexlify(signature)
    data_hash = unhexlify(data_hash)

    verifying_key = VerifyingKey.from_string(public_key_bytes, curve=SECP256k1)
    is_valid = verifying_key.verify(signature_bytes, data_hash)

    print(f"Signature is valid: {is_valid}")
    return is_valid

核心问题原因

  1. 椭圆曲线不匹配:前端使用的是P-256(secp256r1/NIST P-256)曲线,后端错误选用了SECP256k1(比特币专用曲线),两条曲线参数完全不同,公钥无法在错误曲线上通过验证。
  2. 公钥格式不兼容:Web Crypto默认导出的公钥是SPKI格式(ASN.1编码),而ecdsa库的from_string方法默认期待原始公钥(X+Y字节拼接),直接解析会导致格式错误。
  3. 签名格式差异:Web Crypto生成的ECDSA签名是ASN.1 DER编码格式,而ecdsa库默认验证的是r+s原始拼接格式,格式不匹配会导致验证失败。

解决办法

1. 统一椭圆曲线

后端改用与前端一致的P-256曲线,对应ecdsa库中的NIST256p曲线。

2. 对齐公钥格式

方案一:前端导出原始格式公钥

修改前端代码,导出raw格式的公钥(直接获取X+Y字节拼接数据):

// 导出公钥为raw格式(64字节,X和Y各32字节)
const publicKeyRaw = await window.crypto.subtle.exportKey(
    'raw',
    keyPair.publicKey
);
const publicKeyHex = arrayBufferToHex(publicKeyRaw);
// 将publicKeyHex发送至后端

方案二:后端解析SPKI格式公钥

如果前端保持默认导出SPKI格式公钥,后端需要解析该格式(推荐用cryptography库实现)。

3. 适配签名格式

Web Crypto的签名是ASN.1 DER格式,验证时必须指定格式参数。

修正后的ecdsa库验证代码

from ecdsa import VerifyingKey, NIST256p
import hashlib
from binascii import unhexlify

def verify(public_key, signature, data_hash):
    public_key_bytes = unhexlify(public_key)
    signature_bytes = unhexlify(signature)
    data_hash_bytes = unhexlify(data_hash)

    # 使用NIST256p(对应P-256)曲线,指定公钥为raw格式
    verifying_key = VerifyingKey.from_string(
        public_key_bytes, 
        curve=NIST256p, 
        hashfunc=hashlib.sha256
    )
    
    # 指定签名格式为ASN.1 DER
    is_valid = verifying_key.verify(
        signature_bytes, 
        data_hash_bytes, 
        signature_format='der'
    )

    print(f"Signature is valid: {is_valid}")
    return is_valid

备选:用cryptography库验证(更兼容Web Crypto)

如果使用cryptography库,可直接解析SPKI格式公钥,无需修改前端导出逻辑:

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from binascii import unhexlify

def verify(public_key_spki_hex, signature_hex, data_hash_hex):
    public_key_bytes = unhexlify(public_key_spki_hex)
    signature_bytes = unhexlify(signature_hex)
    data_hash_bytes = unhexlify(data_hash_hex)

    # 解析SPKI格式公钥,使用P-256对应曲线
    public_key = ec.EllipticCurvePublicKey.from_encoded_point(
        ec.SECP256R1(),
        public_key_bytes
    )

    try:
        public_key.verify(
            signature_bytes,
            data_hash_bytes,
            ec.ECDSA(hashes.SHA256())
        )
        print("Signature is valid")
        return True
    except:
        print("Signature is invalid")
        return False

注:前端需用exportKey('spki', keyPair.publicKey)导出SPKI格式公钥。


内容的提问来源于stack exchange,提问作者Robin Michel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 07:35:42