kotlinbouncycastleopensshjceed25519

Java/Kotlin Output Ed25519 keypair as both JCE KeyPair instance and in OpenSSH format


I'm generating Ed25519 key pairs in my Kotlin app using the BouncyCastle library and have two requirements that are easy to implement in isolation, but seemingly difficult to do in tandem:

I have two options for generating the keys using BouncyCastle, each makes only one of these requirements easy.

Generate directly using BouncyCastle generator

val generator = Ed25519KeyPairGenerator()
generator.init(Ed25519KeyGenerationParameters(SecureRandom()))
val pair = generator.generateKeyPair()

This gives me a key containing Ed25519PublicKeyParameters, which makes it super easy to get the OpenSSH .pub format using OpenSSHPublicKeyUtil provided by BouncyCastle:

"ssh-ed25519 " + toBase64(OpenSSHPublicKeyUtil.encodePublicKey(publicKey))

...but there is no obvious way to get to a JCE KeyPair from here. The BouncyCastle JCE implementation seems to use BCEdDSAPublicKey and BCEdDSAPrivateKey as wrapper classes for exactly this purpose, but their constructors are package-private.

Generate using BouncyCastle as a JCE security provider

Security.addProvider(BouncyCastleProvider())
val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(EdDSAParameterSpec.Ed25519, BouncyCastleProvider.PROVIDER_NAME)
keyPairGenerator.initialize(EdDSAParameterSpec(EdDSAParameterSpec.Ed25519), SecureRandom())
val pair = keyPairGenerator.generateKeyPair()

This gives me the JCE KeyPair I'm looking for, but no obvious way to convert it to OpenSSH .pub format. The answers in this RSA-specific question all only support DSA/RSA, or suggest libraries that also don't seem to be able to handle the Ed25519 keys. I have tried:

What I think I need

Any one of:


Solution

  • Hack using reflection (wrapped into getter of extension property), following way #2 (get a Ed25519PublicKeyParameters instance from a BCEdDSAPublicKey):

    val BCEdDSAPublicKey.pubKey
        get() = BCEdDSAPublicKey::class.declaredMemberProperties
            .find { it.returnType.javaType == AsymmetricKeyParameter::class.java }!!
            .apply { isAccessible = true }
            .get(this) as AsymmetricKeyParameter