如何在不使用BouncyCastle的原生Java >=11中推导ECDSA公钥
纯Java 11+ 从ECDSA私钥推导公钥的解决方案
很多现有方案依赖BouncyCastle库实现ECDSA私钥到公钥的推导,但如果需要纯Java 11+的原生实现,ChatGPT给出的代码会遇到问题——java.security.spec.ECPoint类并没有提供multiply()方法,无法直接计算生成点与私钥值的点乘。
以下是基于Java标准API的可靠实现,完全不依赖第三方库:
import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.ECFieldFp; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.interfaces.ECPrivateKey; import java.math.BigInteger; public class ECDSAPublicKeyDerivation { public static PublicKey derivePublicKey(PrivateKey privateKey) throws Exception { // 将私钥强制转换为ECDSA私钥类型 ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey; BigInteger privateS = ecPrivateKey.getS(); ECParameterSpec ecSpec = ecPrivateKey.getParams(); ECPoint generator = ecSpec.getGenerator(); // 计算公钥点:Q = G * s(生成点乘以私钥值) ECPoint publicPoint = multiplyPoint(ecSpec, generator, privateS); // 构建公钥规格并生成公钥实例 ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, ecSpec); KeyFactory keyFactory = KeyFactory.getInstance("EC"); return keyFactory.generatePublic(pubKeySpec); } // 用快速幂算法实现椭圆曲线点乘 private static ECPoint multiplyPoint(ECParameterSpec ecSpec, ECPoint point, BigInteger scalar) { // 初始化结果为无穷远点(用(0,0)表示) ECPoint result = new ECPoint(BigInteger.ZERO, BigInteger.ZERO); ECPoint current = point; BigInteger remaining = scalar; while (remaining.compareTo(BigInteger.ZERO) > 0) { if (remaining.testBit(0)) { result = addPoints(ecSpec, result, current); } current = addPoints(ecSpec, current, current); // 点加倍操作 remaining = remaining.shiftRight(1); } return result; } // 实现椭圆曲线点加法逻辑(含点加倍特殊情况) private static ECPoint addPoints(ECParameterSpec ecSpec, ECPoint p1, ECPoint p2) { // 处理无穷远点的特殊情况 if (p1.getAffineX().equals(BigInteger.ZERO) && p1.getAffineY().equals(BigInteger.ZERO)) { return p2; } if (p2.getAffineX().equals(BigInteger.ZERO) && p2.getAffineY().equals(BigInteger.ZERO)) { return p1; } EllipticCurve curve = ecSpec.getCurve(); BigInteger p = ((ECFieldFp) curve.getField()).getP(); BigInteger a = curve.getA(); BigInteger x1 = p1.getAffineX(); BigInteger y1 = p1.getAffineY(); BigInteger x2 = p2.getAffineX(); BigInteger y2 = p2.getAffineY(); BigInteger lambda; // 点加倍(两点坐标相同) if (x1.equals(x2) && y1.equals(y2)) { lambda = x1.pow(2).multiply(BigInteger.valueOf(3)).add(a) .multiply(y1.multiply(BigInteger.valueOf(2)).modInverse(p)) .mod(p); } // 普通点加法 else { lambda = y2.subtract(y1) .multiply(x2.subtract(x1).modInverse(p)) .mod(p); } BigInteger x3 = lambda.pow(2).subtract(x1).subtract(x2).mod(p); BigInteger y3 = lambda.multiply(x1.subtract(x3)).subtract(y1).mod(p); return new ECPoint(x3, y3); } }
代码说明
- 核心流程:从ECDSA私钥中提取私钥值
s和椭圆曲线参数,通过点乘计算公钥点Q,最后用KeyFactory生成公钥实例。 - 点乘实现:采用快速幂算法优化点乘效率,避免直接循环累加带来的性能损耗。
- 点加法逻辑:严格遵循椭圆曲线有限域运算规则,处理了无穷远点、点加倍等特殊场景。
注意事项
- 该实现仅支持**素数域(ECFieldFp)**的椭圆曲线,这是ECDSA常用的曲线类型(如secp256r1、secp384r1等)。
- 所有运算均基于模
p执行,符合椭圆曲线的数学规则。
内容的提问来源于stack exchange,提问作者Hank
相关产品推荐
相关产品推荐

