在 Java 中加载原始的 64 字节长 ECDSA 公钥
我有一个原始(r,s)格式的ECDSA NIST P-256公钥。似乎没有简单的方法可以将其加载到实现java.security.interfaces.ECPublicKey的对象中。
加载 64 字节公钥以便可用于检查签名的最干净方法是什么?
我有一个原始(r,s)格式的ECDSA NIST P-256公钥。似乎没有简单的方法可以将其加载到实现java.security.interfaces.ECPublicKey的对象中。
加载 64 字节公钥以便可用于检查签名的最干净方法是什么?
EC功能需要Java 7,Base 64编码器/解码器需要Java 8,没有其他库 - 只是普通的Java。请注意,这在打印出来时实际上会将公钥显示为命名曲线,这是大多数其他解决方案无法做到的。如果您有最新的运行时,则另一个答案会更干净。
如果我们使用 .因此,让我们作弊一下,改用:ECPublicKeySpec
X509EncodedKeySpec
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。w
04
删除用于创建标头的未压缩点值的 32 字节 X 和 Y 值。这只起作用,因为点的大小是静态的 - 它在末端的位置仅由曲线的大小决定。
现在,该函数中所需的只是将接收的公共点添加到标头中,并通过 为 实现的解码器运行它。generateP256PublicKeyFromFlatW
w
X509EncodedKeySpec
上面的代码使用原始的,未压缩的公共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));
加载 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);
}