javakotlinsecuritycryptographyelliptic-curve

ECPrivateKey to ECPublicKey without BouncyCastle


I'm trying to get the public key of EC from a private key.

I already do it using Python, Go which works fine, but I have not been able to do it in Java/Kotlin.

here is the python version:

import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

private_key_b64 = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAMM3sUprvk/VVF3vXhYcY0Vi9Ay1UPd4GLoBV/htAf2w=="

private_key_der = base64.b64decode(private_key_b64)

private_key = serialization.load_der_private_key(
    private_key_der,
    password=None,
    backend=default_backend()
)

public_key = private_key.public_key()

public_key_der = public_key.public_bytes(
    encoding=serialization.Encoding.DER,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

public_key_base64 = base64.b64encode(public_key_der).decode('utf-8')

print(public_key_base64)

here is the Go version:

    privKeyBase64 := "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAMM3sUprvk/VVF3vXhYcY0Vi9Ay1UPd4GLoBV/htAf2w=="

    privKeyDER, err := base64.StdEncoding.DecodeString(privKeyBase64)
    if err != nil {
        log.Fatalf("Error decoding base64 private key: %v", err)
    }

    privateKey, err := x509.ParsePKCS8PrivateKey(privKeyDER)
    if err != nil {
        log.Fatalf("Error parsing EC private key: %v", err)
    }

    k, ok := privateKey.(*ecdsa.PrivateKey)
    if !ok {
        panic("not ecdsa")
    }

    publicKeyDer, err := x509.MarshalPKIXPublicKey(k.Public())
    if err != nil {
        fmt.Println("Error marshaling public key to DER:", err)
        return
    }
    
    fmt.Printf("Public Key: %s\n", base64.StdEncoding.EncodeToString(publicKeyDer))

here is my Kotlin version:

    val privateKey64 = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAMM3sUprvk/VVF3vXhYcY0Vi9Ay1UPd4GLoBV/htAf2w=="

    val decodedKey = Base64.getDecoder().decode(privateKey64)
    val keySpec = PKCS8EncodedKeySpec(decodedKey)
    val keyFactory = KeyFactory.getInstance("EC")
    val privateKey: PrivateKey = keyFactory.generatePrivate(keySpec)
    val publicKey = keyFactory.generatePublic(keySpec)

    println("Private Key: ${privateKey.encoded.encodeBase64()}")
    println("Public Key: ${publicKey.encoded.encodeBase64()}")

but I have an exception:

java.security.spec.InvalidKeySpecException: Only ECPublicKeySpec and X509EncodedKeySpec supported for EC public keys

how to do it without bouncycastle?


Solution

  • I end up using a version of https://github.com/Archerxy/ecdsa_java code.

    val curve = Ecdsa(Curve.SECP_256_R1)
    
    val keystore = buildKeyStore {
        certificate("key") {
            hash = HashAlgorithm.SHA256
            sign = SignatureAlgorithm.ECDSA
            keySizeInBits = 256
            password = "changeit"
        }
    }
    
    val privateKey = keystore.getKey("key", "changeit".toCharArray()) as ECPrivateKey
    val publicKeyReal = keystore.getCertificate("key").publicKey as ECPublicKey
    
    // create public key from private
    val publicKey = curve.privateKeyToPublicKey(privateKey)
    println("Public Key Match: ${publicKey == publicKeyReal}")
    

    and modified privateKeyToPublicKey to:

    fun privateKeyToPublicKey(privateKey: ECPrivateKey): ECPublicKey {
        val priv = privateKey.s
        if (priv > N) throw RuntimeException("Invalid private key.")
        val p: Array<BigInteger> = fastMultiply(Gx, Gy, NUM[1], priv)
        val z: BigInteger = inv(p[2], P)
        val x = z.pow(2).multiply(p[0]).mod(P)
        val y = z.pow(3).multiply(p[1]).mod(P)
        val w = ECPoint(x, y)
        return KeyFactory.getInstance("EC")
            .generatePublic(ECPublicKeySpec(w, privateKey.params)) as ECPublicKey
    }
    

    note it might include security vulnerabilities and performance issues