先解釋一下物臂,HyperLedger 超級(jí)賬本的公私鑰組成笛丙,私鑰隨機(jī)生成,然后私鑰通過secp256r1算法推導(dǎo)公鑰价说。
java 8以后版本中辆亏,包含關(guān)于EC公鑰的實(shí)現(xiàn)。
原始十六進(jìn)制公鑰是由 前綴 04 + public.x + public.y組成鳖目,大小是65扮叨,不加04是64字節(jié)。順便提一下领迈,私鑰長(zhǎng)度是32字節(jié)彻磁。
取得x 和y數(shù)組的辦法如下:
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
// Get x coordinate
byte[] x = ecPublicKey.getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
// Get Y coordinate
byte[] y = ecPublicKey.getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
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.
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);
}
Usage:
ECPublicKey key = generateP256PublicKeyFromFlatW(w);
System.out.println(key);
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.
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.
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
.
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));
}
Finally, I generated the constant P256_HEAD
head value in base 64 using:
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));
}
called by:
String name = "NIST P-256";
int size = 256;
byte[] head = createHeadForNamedCurve(name, size);
System.out.println(Base64.getEncoder().encodeToString(head));