Deriving ECDSA Public Key from Private Key
I came here with the same problem in Kotlin, so, in case it helps anyone, here's how I derived the PublicKey
from an ECDSA PrivateKey
in Kotlin. This code is based on Lev Knoblock's Java answer and then trial-and-errored until it worked.
In my case I knew that my key used the secp256k1
curve, so I could hard-code that part. I didn't bother to learn how to generalize it to other curves.
import java.security.KeyFactory
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECPrivateKey
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec
import org.bouncycastle.jce.spec.ECPublicKeySpec
import org.bouncycastle.math.ec.ECPoint
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.openssl.PEMParser
fun getECPrivateKeyFromPEM(privatePem: String): ECPrivateKey {
val pemParser = PEMParser(privatePem.reader())
val privateKeyInfo = pemParser.readObject() as PrivateKeyInfo
return JcaPEMKeyConverter().getPrivateKey(privateKeyInfo) as ECPrivateKey
}
fun getKeyPairFromECPrivateKey(privateKey: ECPrivateKey): KeyPair {
val keyFactory: KeyFactory = KeyFactory.getInstance("ECDSA", "BC")
val spec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
val Q: ECPoint = spec.getG().multiply(privateKey.getD())
val publicKey: PublicKey = keyFactory.generatePublic(ECPublicKeySpec(Q, spec))
return KeyPair(publicKey, privateKey)
}
And here's my test harness:
import java.io.StringWriter
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
fun main() {
val privatePem = """
|-----BEGIN PRIVATE KEY-----
|MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg55EMdhNJX+YN/bjN
|Eof91oKqEqD0QidEsRMhHBwSRjShRANCAARnSFpE0LDugORBWlSJz0Zf9e0mR9s6
|tlxSeo1Nbd2vv9LDedm+l/CfZpbyYvPm49DAKDhkUHFIVDd2SsiPrRa7
|-----END PRIVATE KEY-----
""".trimMargin()
val privateKey: ECPrivateKey = getECPrivateKeyFromPEM(privatePem)
val pair: KeyPair = getKeyPairFromECPrivateKey(privateKey)
val pems: String = StringWriter().use {
val pemWriter = JcaPEMWriter(it)
pemWriter.writeObject(pair.getPublic())
pemWriter.writeObject(pair.getPrivate())
pemWriter.flush()
it.toString()
}
println(pems)
}
This prints out:
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcE4HMAHLDvPr6xHKsjhPXJzTdxLlRRR8
BfYnI2TGb0QLTFyyXm13CeYiLnxbkGhSvz9ZRo0zGQygKPVpgiThSw==
-----END PUBLIC KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOeRDHYTSV/mDf24zRKH/daCqhKg9EInRLETIRwcEkY0oAoGCCqGSM49
AwEHoUQDQgAEZ0haRNCw7oDkQVpUic9GX/XtJkfbOrZcUnqNTW3dr7/Sw3nZvpfw
n2aW8mLz5uPQwCg4ZFBxSFQ3dkrIj60Wuw==
-----END EC PRIVATE KEY-----
Building on the previous answer one can extend this to the case that all one is given is a private key satisfying BCECPrivateKey interface:
DerivePubKeyFromPrivKey(BCECPrivateKey definingKey, Provider provider) {
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
BigInteger d = definingKey.getD();
org.bouncycastle.jce.spec.ECParameterSpec ecSpec =
definingKey.getParameters();
ECPoint Q = definingKey.getParameters().getG().multiply(d);
org.bouncycastle.jce.spec.ECPublicKeySpec pubSpec = new
org.bouncycastle.jce.spec.ECPublicKeySpec(Q, ecSpec);
PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
return publicKeyGenerated;
}
So after a while, I figured out a solution and decided to post it in case anyone else has the same issue as me:
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint Q = ecSpec.getG().multiply(((org.bouncycastle.jce.interfaces.ECPrivateKey) this.privateKey).getD());
ECPublicKeySpec pubSpec = new ECPublicKeySpec(Q, ecSpec);
PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
this.publicKey = publicKeyGenerated;
EDIT: Removed the code decoding the ECPoint as per @MaartenBodewes comment.