Skip to content
Advertisement

How to create an OpenSSH compatible ED25519 key with Bouncy Castle?

How can you create an OpenSSH ED25519 private key that can be used for SSH? The goal would be to have a key file in the same format same like you would have in .ssh/id_ed25519 for your OpenSSH client.

This is my current approach, which does not create a compatible:

import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

import java.io.StringWriter;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;

public class Test {
    static {
        Security.removeProvider("BC");provider
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
    }

    public static String createCurve25519PEM() {
        try {
            X9ECParameters curveParams = CustomNamedCurves.getByName("Curve25519");
            ECParameterSpec ecSpec = new ECParameterSpec(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH(), curveParams.getSeed());
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            kpg.initialize(ecSpec);
            KeyPair keypair = kpg.generateKeyPair();

            AsymmetricKeyParameter akp = PrivateKeyFactory.createKey(keypair.getPrivate().getEncoded());
            byte[] content = OpenSSHPrivateKeyUtil.encodePrivateKey(akp);
            PemObject o = new PemObject("OPENSSH PRIVATE KEY", content);
            StringWriter sw = new StringWriter();
            PemWriter w = new PemWriter(sw);
            w.writeObject(o);
            w.close();
            Log.d("createCurve25519PEM", "key: " + sw.toString());
            return sw.toString();
        } catch (Exception e) {
            Log.d("createCurve25519PEM", e.toString());
        }
        return null;
    }
}

The output looks like this:

-----BEGIN OPENSSH PRIVATE KEY-----
MIIBTwIBAQQgA8BjYjSjUgM4PahSZQx3i9DWcEdGiGnBoA0tXCUENzKggeEwgd4C
AQEwKwYHKoZIzj0BAQIgf////////////////////////////////////////+0w
RAQgKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmEkUoUQEIHtCXtCXtCXtCXtC
XtCXtCXtCXtCXtCXtCYLXpx3EMhkBEEEKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqtJFogrhmhuKCGtOAe3Sx3SNFMkj1Nfm18YbIp6cWifs7T2QIgEAAAAAAA
AAAAAAAAAAAAABTe+d6i95zWWBJjGlz10+0CAQihRANCAARl0Kc+dO0Er1dpu6mh
/lZmTw3/DMKPLTzjosX2u7hQswV+U9o0WOYFd1JOqsGdkLfYuGmdZzWdk74dvV1O
+w5T
-----END OPENSSH PRIVATE KEY-----

.. but is unfortunately not accepted by SSH.

Advertisement

Answer

Using “Curve25519” that way gives it to you in the short Weierstrass form used by X9; this is not usable for Ed25519, which is defined to use twisted-Edwards form. Moreover cramming it through JCA’s unfortunate ECParameterSpec class gives the original X9-defined ‘explicit’ representation which is now obsolete and almost never used even for algorithms which do use Weierstrass curves. As a result the data you create is not correct for PEM type OPENSSH PRIVATE KEY; it is valid for OpenSSL’s ‘traditional’ PEM type EC PRIVATE KEY, and OpenSSL (which does still support, though not prefer, “param_enc explicit”) is able to read that content with that type, though it can’t be used to interoperate with anything else.

You need to either use JCA with algorithm “ED25519” (not “EC” “ECDSA” “ECDH” “ECMQV” etc which are all X9/SECG) something like this:

    KeyPair pair = KeyPairGenerator.getInstance("ED25519","BC") .generateKeyPair();
    AsymmetricKeyParameter bprv = PrivateKeyFactory.createKey(pair.getPrivate().getEncoded());
    // then proceed as you already have; I have simplified for my test environment
    byte[] oprv = OpenSSHPrivateKeyUtil.encodePrivateKey(bprv);
    PemWriter w = new PemWriter(new OutputStreamWriter(System.out));
    w.writeObject(new PemObject("OPENSSH PRIVATE KEY", oprv)); w.close();

    // BTW if you pass an actual Provider to .getInstance (rather than a String, or letting it search)
    // you don't actually need to have put that Provider in the searchlist
    // Also in Java 15 up you can use the SunEC provider (normally searched by default)
    // but since you still need other Bouncy pieces why bother

or since you’re already dependent on Bouncy use the lightweight API:

    AsymmetricCipherKeyPairGenerator gen = new Ed25519KeyPairGenerator(); 
    gen.init(new KeyGenerationParameters(new SecureRandom(), 255));
    AsymmetricKeyParameter bprv = gen.generateKeyPair().getPrivate();
    // ditto
Advertisement