在Java中加载原始的64字节长ECDSA公钥

我有一个原始(r,s)格式的ECDSA NIST P-256公钥。 似乎没有简单的方法将它加载到实现java.security.interfaces.ECPublicKey的对象中。

加载64字节公钥的最简洁方法是什么,以便可以用来检查签名?

如果我们使用ECPublicKeySpec执行此操作,这个答案将会很艰难。 所以让我们作弊:

 private static byte[] P256_HEAD = Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE"); public static ECPublicKey convertP256Key(byte[] w) throws InvalidKeySpecException { byte[] encodedKey = new byte[P256_HEAD.length + w.length]; System.arraycopy(P256_HEAD, 0, encodedKey, 0, P256_HEAD.length); System.arraycopy(w, 0, encodedKey, P256_HEAD.length, w.length); KeyFactory eckf; try { eckf = KeyFactory.getInstance("EC"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("EC key factory not present in runtime"); } X509EncodedKeySpec ecpks = new X509EncodedKeySpec(encodedKey); return (ECPublicKey) eckf.generatePublic(ecpks); } 

用法:

 ECPublicKey key = convertP256Key(w); System.out.println(key); 

我使用以下方法生成头部:

 private static byte[] createHeadForNamedCurve(String name, int size) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); ECGenParameterSpec m = new ECGenParameterSpec(name); kpg.initialize(m); KeyPair kp = kpg.generateKeyPair(); byte[] encoded = kp.getPublic().getEncoded(); return Arrays.copyOf(encoded, encoded.length - 2 * (size / Byte.SIZE)); } 

叫做:

 String name = "NIST P-256"; int size = 256; byte[] head = createHeadForNamedCurve(name, size); System.out.println(Base64.getEncoder().encodeToString(head)); 

这背后的想法是创建一个X509编码密钥,它最终以公共点w结束(之前的字节包含指定曲线的OID的ASN.1 DER编码和结构开销,以字节04结尾)表示未压缩的点)。 然后我们用你的w替换最后的“随机”点w ,我们再次解码它。

ECfunction需要Java 7,Base 64编码器/解码器需要Java 8,没有额外的库和东西。 请注意,这实际上会在打印时将公钥显示为命名曲线 ,而其他解决方案则不会这样做。

Java确实使密码学很长时间。

从给定EC点创建公钥的过程:

  1. 根据给定的坐标构造ECPoint对象。
  2. 根据曲线信息构造ECParameterSpec对象。
  3. 从您的ECPoint和您的ECParameterSpec对象构造一个ECParameterSpec对象。
  4. 使用您的ECPublicKeySpec对象调用KeyFactory.generatePublic()以检索PublicKey对象。
  5. 必要时将PublicKey转换为ECPublicKey

示例如下:

 // Setup for P-256 curve params BigInteger p256_p = new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16); BigInteger p256_a = new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16); BigInteger p256_b = new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16); byte[] p256_seed = { (byte) 0xc4, (byte) 0x9d, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xe7, (byte) 0x04, (byte) 0x93, (byte) 0x6a, (byte) 0x66, (byte) 0x78, (byte) 0xe1, (byte) 0x13, (byte) 0x9d, (byte) 0x26, (byte) 0xb7, (byte) 0x81, (byte) 0x9f, (byte) 0x7e, (byte) 0x90 }; BigInteger p256_xg = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16); BigInteger p256_yg = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16); BigInteger p256_n = new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16); // Construct prime field ECFieldFp p256_field = new ECFieldFp(p256_p); // Construct curve from parameters EllipticCurve p256 = new EllipticCurve(p256_field, p256_a, p256_b, p256_seed); // Construct base point for curve ECPoint p256_base = new ECPoint(p256_xg, p256_yg); // Construct curve parameter specifications object ECParameterSpec p256spec = new ECParameterSpec(p256, p256_base, p256_n, 1); // Co-factor 1 for prime curves // ------------------------------------------------------------- // // Construct EC point from "raw" public key ECPoint point = new ECPoint(r, s); // r, s is of type BigInteger // Create a EC public key specification object from point and curve ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, p256spec); // Retrieve EC KeyFactory KeyFactory ECFactory = KeyFactory.getInstance("EC"); // Generate public key via KeyFactory PublicKey pubKey = ECFactory.generatePublic(pubKeySpec); ECPublicKey ECPubKey = (ECPublicKey) pubKey; 

出于性能原因,生成ECParameterSpec(可能在静态初始化程序块中)可能会有所帮助。

注意:可能有一种更简单的方法来生成ECParameterSpec对象(例如通过命名曲线)但到目前为止我只发现ECGenParameterSpec具有此function。 如果有一个不那么痛苦的方法,请在评论中告诉我。


为了省去上述操作的痛苦,请在X.509下对EC密钥进行编码,这将完全描述密钥并使加载更加容易。

在java中,使用ECPublicKey,您需要做的就是调用ECPublicKey.getEncoded()并将字节数组传递/保存到您需要密钥的位置。 然后可以通过以下方式重建X.509编码密钥:

 // Retrieve EC KeyFactory KeyFactory ECFactory = KeyFactory.getInstance("EC"); // Generate public key via KeyFactory PublicKey pubKey = ECFactory.generatePublic(new X509EncodedKeySpec(data)); ECPublicKey ECPubKey = (ECPublicKey) pubKey; 

其中“data”是编码的字节数组。

EC公钥是由x和y坐标组成的点。 我编写了以下代码段一次,将EC x,y指向publicKey对象。 希望这会帮助你。 供你参考:

rawPubKey = 04 + x坐标+ y坐标(Hex String)

curveName = P-256(String)

示例EC P-256的公钥点:

rawPubKey = 04 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5

BC Provider:您需要Bouncy Castle提供商。 我用过bcprov-jdk15on-149.jar ,但你可以从这里下载最新版本。

 /** * This method converts the uncompressed raw EC public key into java.security.interfaces.ECPublicKey * @param rawPubKey * @param curveName * @return java.security.interfaces.ECPublicKey */ public ECPublicKey ucPublicKeyToPublicKey(String rawPubKey, String curveName) { byte[] rawPublicKey = Helper.toByte(rawPubKey); ECPublicKey ecPublicKey = null; KeyFactory kf = null; ECNamedCurveParameterSpec ecNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec(curveName); ECCurve curve = ecNamedCurveParameterSpec.getCurve(); EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, ecNamedCurveParameterSpec.getSeed()); java.security.spec.ECPoint ecPoint = ECPointUtil.decodePoint(ellipticCurve, rawPublicKey); ECParameterSpec ecParameterSpec = EC5Util.convertSpec(ellipticCurve, ecNamedCurveParameterSpec); java.security.spec.ECPublicKeySpec publicKeySpec = new java.security.spec.ECPublicKeySpec(ecPoint, ecParameterSpec); kf = java.security.KeyFactory.getInstance("EC"); try { ecPublicKey = (ECPublicKey) kf.generatePublic(publicKeySpec); } catch (Exception e) { System.out.println("Caught Exception public key: " + e.toString()); } return ecPublicKey; } 

编辑:这是toByte()方法:

 public static byte[] toByte(String hex) { if (hex == null) return null; hex = hex.replaceAll("\\s", ""); byte[] buffer = null; if (hex.length() % 2 != 0) { hex = "0" + hex; } int len = hex.length() / 2; buffer = new byte[len]; for (int i = 0; i < len; i++) { buffer[i] = (byte) Integer.parseInt( hex.substring(i * 2, i * 2 + 2), 16); } return buffer; } 

但您可以使用自己的实现。 这是另一个:

 import javax.xml.bind.DatatypeConverter; public static byte[] toByte(String hex) {{ return DatatypeConverter.parseHexBinary(hex); } 

在Bouncycastle的帮助下,这对我有用:

 ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1"); ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN()); ECPoint publicPoint = ECPointUtil.decodePoint(params.getCurve(), publicKeyByteArray); ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params); PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);