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
相关产品推荐
相关产品推荐

