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; }
请问这段代码存在什么问题?
问题原因及解决方案
你的代码存在两个核心问题:
未指定Spongy Castle作为Security Provider
Android系统默认的Security Provider对secp256k1这类非NIST标准曲线的ECDSA支持不完善,且和Spongy Castle的实现不兼容。必须在初始化KeyFactory和Signature时明确指定使用Spongy Castle的Provider。消息哈希逻辑不匹配
在线工具验证时,通常是对消息的十六进制字符串本身做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
相关产品推荐
相关产品推荐

