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

Android中使用Spongy Castle进行ECDSA签名验证失败问题排查

Android中Spongy Castle ECDSA签名验证始终返回false问题

我在Android Flamingo中尝试使用Spongy Castle验证ECDSA签名,已在Gradle中添加依赖:

implementation group: 'com.madgag.spongycastle', name: 'bcpkix-jdk15on', version: '1.58.0.0'

使用secp256k1曲线,采用十六进制格式的公钥、消息和签名执行代码时,verified始终返回false,但这些内容通过在线工具验证均有效。

代码如下:

String publicKeyHex = "04954ed5969f7d4cc2baa1f0345f037a4758e84058d202feb64885c32dfc62123af4c8ccfba16088fc9cc2bd84594d810ad470706cfddfc17999d291b038f4c9f1";
String messageHex   = "12ca8b3b17607820279c7e0095d655d9ce289e90cccc0e99ae00f5e29f8d6615";
String signatureHex = "3045022100affcf495d11d02fcde6f9cd502975b78dd18b4b50127edef56683ca8534575cf02206f3d92bb26d515d122754c88e92c1d7d8bfc2b35876608b9c75ee9cbcd6fdde9";

byte[] publicKeyBytes = hexStringToByteArray(publicKeyHex);
byte[] messageBytes   = hexStringToByteArray(messageHex);
byte[] signatureBytes = hexStringToByteArray(signatureHex);

final ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
final ECNamedCurveSpec params = new ECNamedCurveSpec("secp256k1", spec.getCurve(), spec.getG(),spec.getN());

KeyFactory keyFactory = KeyFactory.getInstance("EC");
ECPoint ecPoint = ECPointUtil.decodePoint(params.getCurve(), publicKeyBytes);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ecPoint, params);
ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(pubKeySpec);

Signature signature = Signature.getInstance("Sha256withECDSA");
signature.initVerify(publicKey);
signature.update(messageBytes);
boolean verified = signature.verify(signatureBytes);

hexStringToByteArray方法定义:

public static final byte[] hexStringToByteArray(final String str) {
    int len = str.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2)
        data[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i+1), 16));
    return data;
}

请问这段代码存在什么问题?


问题原因及解决方案

你的代码存在两个核心问题:

  1. 未指定Spongy Castle作为Security Provider
    Android系统默认的Security Provider对secp256k1这类非NIST标准曲线的ECDSA支持不完善,且和Spongy Castle的实现不兼容。必须在初始化KeyFactory和Signature时明确指定使用Spongy Castle的Provider。

  2. 消息哈希逻辑不匹配
    在线工具验证时,通常是对消息的十六进制字符串本身做SHA-256哈希后签名,但你的代码直接把十六进制转成原始字节传给signature.update()——而Sha256withECDSA算法会自动对输入做SHA-256哈希,相当于做了两次哈希,导致验证失败。

修改后的代码(匹配在线工具逻辑)

import org.spongycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.nio.charset.StandardCharsets;

// 建议在App初始化时仅执行一次Provider注册
static {
    Security.addProvider(new BouncyCastleProvider());
}

// 验证逻辑
String publicKeyHex = "04954ed5969f7d4cc2baa1f0345f037a4758e84058d202feb64885c32dfc62123af4c8ccfba16088fc9cc2bd84594d810ad470706cfddfc17999d291b038f4c9f1";
String messageHex   = "12ca8b3b17607820279c7e0095d655d9ce289e90cccc0e99ae00f5e29f8d6615";
String signatureHex = "3045022100affcf495d11d02fcde6f9cd502975b78dd18b4b50127edef56683ca8534575cf02206f3d92bb26d515d122754c88e92c1d7d8bfc2b35876608b9c75ee9cbcd6fdde9";

byte[] publicKeyBytes = hexStringToByteArray(publicKeyHex);
// 直接使用消息十六进制字符串的UTF-8字节,而非转成原始字节
byte[] messageBytes = messageHex.getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = hexStringToByteArray(signatureHex);

final ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
final ECNamedCurveSpec params = new ECNamedCurveSpec("secp256k1", spec.getCurve(), spec.getG(), spec.getN());

// 指定Spongy Castle Provider
KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
ECPoint ecPoint = ECPointUtil.decodePoint(params.getCurve(), publicKeyBytes);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ecPoint, params);
ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(pubKeySpec);

// 指定Spongy Castle Provider
Signature signature = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
signature.initVerify(publicKey);
signature.update(messageBytes);
boolean verified = signature.verify(signatureBytes);

备选方案(若签名是对原始消息哈希生成)

如果签名是基于“十六进制转字节后的消息”的SHA-256哈希生成的,需手动计算哈希并使用NONEwithECDSA算法:

import java.security.MessageDigest;

// 手动计算消息哈希
MessageDigest digest = MessageDigest.getInstance("SHA-256", BouncyCastleProvider.PROVIDER_NAME);
byte[] messageHash = digest.digest(hexStringToByteArray(messageHex));

// 使用NONEwithECDSA,不自动做哈希
Signature signature = Signature.getInstance("NONEwithECDSA", BouncyCastleProvider.PROVIDER_NAME);
signature.initVerify(publicKey);
signature.update(messageHash);
boolean verified = signature.verify(signatureBytes);

两种方案均可解决验证失败问题,具体选择取决于签名生成时的逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:47:03