I'm trying to generate a public key given a private key and known curve. Below is my code:
// Generate Keys
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC");
keyPairGenerator.initialize(ecGenSpec, new SecureRandom());
java.security.KeyPair pair = keyPairGenerator.generateKeyPair();
ECPrivateKey privateKey = (ECPrivateKey) pair.getPrivate();
ECPublicKey publicKeyExpected = (ECPublicKey) pair.getPublic();
// Expected public key
System.out.print("Expected Public Key: " +
BaseEncoding.base64Url().encode(publicKeyExpected.getEncoded()));
// Generate public key from private key
X9ECParameters ecp = SECNamedCurves.getByName("secp256r1");
ECDomainParameters domainParams = new ECDomainParameters(ecp.getCurve(),
ecp.getG(), ecp.getN(), ecp.getH(),
ecp.getSeed());
ECPoint Q = domainParams.getG().multiply(privateKey.getS()); // is this correct?
KeyFactory kf = KeyFactory.getInstance("ECDSA", "BC");
ECPublicKey publicKeyGenerated =
(ECPublicKey) kf.generatePublic(new X509EncodedKeySpec(Q.getEncoded(false))); // exception here
// Generated public key from private key
System.out.print("Generated Public Key: " +
BaseEncoding.base64Url().encode(publicKeyGenerated.getEncoded()));
However, when I call: kf.generatePublic(new X509EncodedKeySpec(Q.getEncoded(false)))
I get the exception: java.security.spec.InvalidKeySpecException: encoded key spec not recognised
(not my misspelling)
I appears I'm incorrectly calculating Q
, but I'm not sure where my error is.
Thanks for the help!
In case my future self needs the solution:
// Generate Keys
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC");
keyPairGenerator.initialize(ecGenSpec, new SecureRandom());
java.security.KeyPair pair = keyPairGenerator.generateKeyPair();
ECPrivateKey privateKey = (ECPrivateKey) pair.getPrivate();
ECPublicKey publicKeyExpected = (ECPublicKey) pair.getPublic();
// Expected public key
System.out.print("Expected Public Key: " +
BaseEncoding.base64Url().encode(publicKeyExpected.getEncoded()));
// Generate public key from private key
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECPoint Q = ecSpec.getG().multiply(privateKey.getD());
byte[] publicDerBytes = Q.getEncoded(false);
ECPoint point = ecSpec.getCurve().decodePoint(publicDerBytes);
ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, ecSpec);
ECPublicKey publicKeyGenerated = (ECPublicKey) keyFactory.generatePublic(pubSpec);
// Generated public key from private key
System.out.print("Generated Public Key: " +
BaseEncoding.base64Url().encode(publicKeyGenerated.getEncoded()));
Some more improvement to the answer of @markw, extended with curve name detection by @bas-goossen from here: How to find the matching curve name from an ECPublicKey
public static ECPublicKey publicFromPrivate(final ECPrivateKey privateKey) throws Exception {
ECParameterSpec params = privateKey.getParams();
org.bouncycastle.jce.spec.ECParameterSpec bcSpec = org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util
.convertSpec(params, false);
org.bouncycastle.math.ec.ECPoint q = bcSpec.getG().multiply(privateKey.getS());
org.bouncycastle.math.ec.ECPoint bcW = bcSpec.getCurve().decodePoint(q.getEncoded(false));
ECPoint w = new ECPoint(
bcW.getAffineXCoord().toBigInteger(),
bcW.getAffineYCoord().toBigInteger());
ECPublicKeySpec keySpec = new ECPublicKeySpec(w, tryFindNamedCurveSpec(params));
return (ECPublicKey) KeyFactory
.getInstance("EC", org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME)
.generatePublic(keySpec);
}
@SuppressWarnings("unchecked")
public static ECParameterSpec tryFindNamedCurveSpec(ECParameterSpec params) {
org.bouncycastle.jce.spec.ECParameterSpec bcSpec
= org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertSpec(params, false);
for (Object name : Collections.list(org.bouncycastle.jce.ECNamedCurveTable.getNames())) {
org.bouncycastle.jce.spec.ECNamedCurveParameterSpec bcNamedSpec
= org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec((String) name);
if (bcNamedSpec.getN().equals(bcSpec.getN())
&& bcNamedSpec.getH().equals(bcSpec.getH())
&& bcNamedSpec.getCurve().equals(bcSpec.getCurve())
&& bcNamedSpec.getG().equals(bcSpec.getG())) {
return new org.bouncycastle.jce.spec.ECNamedCurveSpec(
bcNamedSpec.getName(),
bcNamedSpec.getCurve(),
bcNamedSpec.getG(),
bcNamedSpec.getN(),
bcNamedSpec.getH(),
bcNamedSpec.getSeed());
}
}
return params;
}