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

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

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


答案 1

EC功能需要Java 7,Base 64编码器/解码器需要Java 8,没有其他库 - 只是普通的Java。请注意,这在打印出来时实际上会将公钥显示为命名曲线,这是大多数其他解决方案无法做到的。如果您有最新的运行时,则另一个答案会更干净。

如果我们使用 .因此,让我们作弊一下,改用:ECPublicKeySpecX509EncodedKeySpec

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编码密钥,它很高兴地以结尾的公共点结束。前面的字节包含命名曲线和结构开销的 OID 的 ASN.1 DER 编码,以指示未压缩点的字节结尾。下面是一个结构示例,将值 1 和 2 用于 32 字节 X 和 Y。w04

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

现在,该函数中所需的只是将接收的公共点添加到标头中,并通过 为 实现的解码器运行它。generateP256PublicKeyFromFlatWwX509EncodedKeySpec


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

/**
 * 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));
}

最后,我使用以下方法生成了以 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));

答案 2

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

我能召集的最干净的!也应该与其他曲线一起使用。

注意:仅限于 SunJCE 提供商或 Android API 26+(可能还有更多具有此功能的提供商,我目前还不知道它们。

public static ECPublicKey rawToEncodedECPublicKey(String curveName, byte[] rawBytes) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException {
    KeyFactory kf = KeyFactory.getInstance("EC");
    byte[] x = Arrays.copyOfRange(rawBytes, 0, rawBytes.length/2);
    byte[] y = Arrays.copyOfRange(rawBytes, rawBytes.length/2, rawBytes.length);
    ECPoint w = new ECPoint(new BigInteger(1,x), new BigInteger(1,y));
    return (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(w, ecParameterSpecForCurve(curveName)));
}

public static ECParameterSpec ecParameterSpecForCurve(String curveName) throws NoSuchAlgorithmException, InvalidParameterSpecException {
    AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
    params.init(new ECGenParameterSpec(curveName));
    return params.getParameterSpec(ECParameterSpec.class);
}

推荐