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:
KeyPair
instance for use with a third-party SSH libraryssh-ed25519 <encoded key> <comment>
)I have two options for generating the keys using BouncyCastle, each makes only one of these requirements easy.
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.
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:
Any one of:
AsymmetricCipherKeyPair
to a JCE KeyPair
Ed25519PublicKeyParameters
instance from a BCEdDSAPublicKey
wrapper so I can use BouncyCastle's OpenSSH utility methodKeyPair
in OpenSSH formatHack 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