0
votes

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:

  • Provide the key pair as a JCE KeyPair instance for use with a third-party SSH library
  • Provide the public key in OpenSSH .pub format for the user to copy and paste into a git repository provider such as GitHub (i.e. ssh-ed25519 <encoded key> <comment>)

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:

  • Apache SSHD
  • SSHJ
  • Jsch

What I think I need

Any one of:

  • A way to convert from BouncyCastle's AsymmetricCipherKeyPair to a JCE KeyPair
  • A way to get a Ed25519PublicKeyParameters instance from a BCEdDSAPublicKey wrapper so I can use BouncyCastle's OpenSSH utility method
  • Another way to output a BouncyCastle generated Ed25519 public key from a KeyPair in OpenSSH format
  • Another way/library to generate an Ed25519 keypair that will support my two requirements
1

1 Answers

1
votes

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