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

NodeJS生成P-256公钥在Java验签时抛出Invalid point encoding异常

ECDSA验签异常:Invalid point encoding 0x30 问题排查

问题背景

我用Node.js v20.14.0生成P-256曲线的EC公钥、签名及对应的R、S值,代码如下:

const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { 
  namedCurve:  'P-256', //'secp256k1', // invalid 'secp256r1', 
  publicKeyEncoding: { type: 'spki', format: 'der' }
});
const sign = crypto.createSign('SHA256');
const message = 'my message';
sign.update(message);
sign.end();
const signature = sign.sign(privateKey);
const RLength = parseInt(signature.toString('hex', 3, 4), 16);
const R = signature.subarray(4, 4+RLength);
const S = signature.subarray(4+RLength+2, signature.length);

示例输出结果

  • 公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL+RfybPlDY/+KMwY3ROpD4aSgtJFxnPOraWQpJkMJ0Ovj4rkrOSMPj+5rhE3jCJNYOV35TIGomJSxI65shfKug==
  • 签名:MEUCIQD+SjM+JD2u91p2Fy8UKtkMqXCkSPaCCIDFBaqzVbDA6QIgU5pGg8qnt7iWHpL9Anw5uxLXTH64gj9V9o0HjEmsBWo=
  • R值:AP5KMz4kPa73WnYXLxQq2QypcKRI9oIIgMUFqrNVsMDp
  • S值:U5pGg8qnt7iWHpL9Anw5uxLXTH64gj9V9o0HjEmsBWo=

Java验签代码及异常

将上述值传入Java 11(基于Bouncy Castle 1.78)验签,代码如下:

String pkey = "MFkwEw...Kug=="; // 上述完整base64字符串
byte[] publicKey = Base64.getDecoder().decode(pkey);
X9ECParameters curve = NISTNamedCurves.getByName("P-256"); //"secp256k1"
ECDomainParameters domain = new ECDomainParameters(curve.getCurve(), curve.getG(), curve.getN(), curve.getH());
ECDSASigner signer = new ECDSASigner(); // 使用Bouncy Castle 1.78
signer.init (
    false, 
    new ECPublicKeyParameters(
        curve.getCurve().decodePoint(publicKey),  // 抛出异常的位置
        domain
    )
);
signer.verifySignature(
    message, 
    new BigInteger(Base64.getDecoder().decode(R)), 
    new BigInteger(Base64.getDecoder().decode(S))
);

执行时抛出异常:java.lang.IllegalArgumentException: Invalid point encoding 0x30

问题原因

问题出在你直接把SPKI格式的完整公钥传给了decodePoint方法——这个方法只接受裸的椭圆曲线点(压缩或非压缩格式),而SPKI是包含算法标识、曲线参数等元数据的ASN.1结构,开头的0x30是ASN.1 SEQUENCE的标签,自然会被判定为无效的点编码。

解决方案

需要先解析SPKI格式的公钥,提取出里面的椭圆曲线点,再传给decodePoint。可以用Bouncy Castle的SubjectPublicKeyInfo类来处理:

修改后的Java验签代码:

import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;

import java.security.Security;
import java.util.Base64;
import java.math.BigInteger;

public class ECDSAVerify {
    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        
        String pkey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL+RfybPlDY/+KMwY3ROpD4aSgtJFxnPOraWQpJkMJ0Ovj4rkrOSMPj+5rhE3jCJNYOV35TIGomJSxI65shfKug==";
        String rStr = "AP5KMz4kPa73WnYXLxQq2QypcKRI9oIIgMUFqrNVsMDp";
        String sStr = "U5pGg8qnt7iWHpL9Anw5uxLXTH64gj9V9o0HjEmsBWo=";
        String message = "my message";
        
        // 解析SPKI格式的公钥
        byte[] spkiBytes = Base64.getDecoder().decode(pkey);
        SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(spkiBytes);
        X9ECParameters curveParams = NISTNamedCurves.getByName("P-256");
        ECDomainParameters domainParams = new ECDomainParameters(
            curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH()
        );
        
        // 提取椭圆曲线点并构建公钥参数
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(
            domainParams.getCurve().decodePoint(spki.getPublicKeyData().getBytes()),
            domainParams
        );
        
        // 初始化验签器并执行验签
        ECDSASigner signer = new ECDSASigner();
        signer.init(false, publicKeyParams);
        
        byte[] messageBytes = message.getBytes("UTF-8");
        // 解码R、S时传入1,确保按正整数解析(避免字节开头为0x00时被识别为负数)
        BigInteger r = new BigInteger(1, Base64.getDecoder().decode(rStr));
        BigInteger s = new BigInteger(1, Base64.getDecoder().decode(sStr));
        
        boolean verified = signer.verifySignature(messageBytes, r, s);
        System.out.println("验签结果:" + verified);
    }
}

额外注意事项:

  • 确保项目已正确引入Bouncy Castle依赖(Maven可引入bcprov-jdk15on)
  • 解码R、S值时必须传入1作为BigInteger构造的第一个参数,保证字节数组被解析为正整数

补充有效测试数据

公钥 (SPKI):     MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/UREdRdAbtwg/HeUhb8B2rICVjIT7ZtxloBVNrBV0jiMCOLmVs8gm0LE7oWlPfBToaWsbfp2sCCR5EhhalGeHQ== 
签名 (ASN.1/DER): MEYCIQDLeC2T8ZIlruOw3Km0THK8yxcMPS5ufOHqus3Y/vyYMgIhAKgoAIV50yXYGIARdNE4bEt+eaujKFwX2cX6pf/y681p 
R:                     AMt4LZPxkiWu47DcqbRMcrzLFww9Lm584eq6zdj+/Jgy 
S:                     AKgoAIV50yXYGIARdNE4bEt+eaujKFwX2cX6pf/y681p
message:               my message

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:45:54