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

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

I have a raw (r,s) format ECDSA NIST P-256 public key. It seems that there is no simple way to load it into an object that implements java.security.interfaces.ECPublicKey.

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

What is the cleanest way to load a 64 byte public key so that it can be used to check signatures?

推荐答案

EC 功能需要 Java 7,Base 64 编码器/解码器需要 Java 8,无需额外的库 - 只需普通 Java.请注意,当打印出来时,这实际上会将公钥显示为 命名曲线,这是大多数其他解决方案所不具备的.如果您有最新的运行时,其他答案会更干净.

Java 7 is required for the EC functionality and Java 8 for the Base 64 encoder / decoder, no additional libraries - just plain Java. Note that this will actually display the public key as a named curve when printed out, something most other solutions won't do. If you have an up-to-date runtime, this other answer is more clean.

如果我们使用 ECPublicKeySpec 来做这件事,这个答案会很困难.所以让我们作弊并使用 X509EncodedKeySpec 代替:

This answer is going to be tough if we do this using ECPublicKeySpec. So lets cheat a bit and use X509EncodedKeySpec instead:

private static byte[] P256_HEAD = Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point consisting of just a 256-bit X and Y
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromFlatW(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 = generateP256PublicKeyFromFlatW(w);
System.out.println(key);

<小时>

这背后的想法是创建一个临时的 X509 编码密钥,它以公共点 w 结尾.之前的字节包含命名曲线的 OID 和结构开销的 ASN.1 DER 编码,以字节 04 结尾,指示未压缩点.这里是中的结构看起来像什么的例子,使用值1和2的32字节的X和Y.


The idea behind this is to create a temporary X509 encoded key, which happily ends with the public point w at the end. The bytes before that contain the ASN.1 DER encoding of the OID of the named curve and structural overhead, ending with byte 04 indicating an uncompressed point. Here is an example what the structure looks like, using value 1 and 2 for the 32-byte X and Y.

为创建标头而删除的未压缩点值的 32 字节 X 和 Y 值.这只有效,因为该点是静态大小的 - 它在末端的位置仅由曲线的大小决定.

The 32-byte X and Y values of the uncompressed point values removed to create the header. This only works because the point is statically sized - it's location at the end is only determined by the size of the curve.

现在函数 generateP256PublicKeyFromFlatW 所需要的只是将接收到的公共点 w 添加到标头中,并通过为 X509EncodedKeySpec<实现的解码器运行它/代码>.

Now all that is required in the function generateP256PublicKeyFromFlatW is to add the received public point w to the header and run it through the decoder implemented for X509EncodedKeySpec.

上面的代码使用了一个原始的、未压缩的公共 EC 点——只有一个 32 字节的 X 和 Y——没有带有值 04 的未压缩点指示符.当然支持 65 字节压缩点也很容易:

The above code uses a raw, uncompressed public EC point - just a 32 byte X and Y - without the uncompressed point indicator with value 04. Of course it is easy to support 65 byte compressed points as well:

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point starting with <code>04</code>
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromUncompressedW(byte[] w) throws InvalidKeySpecException {
    if (w[0] != 0x04) {
        throw new InvalidKeySpecException("w is not an uncompressed key");
    }
    return generateP256PublicKeyFromFlatW(Arrays.copyOfRange(w, 1, w.length));
}

<小时>

最后,我生成了 base 64 中的常量 P256_HEAD 头值:

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));

相关文章