Java 如何使用Bouncy Castle创建与OpenSSH兼容的ED25519密钥?

Java 如何使用Bouncy Castle创建与OpenSSH兼容的ED25519密钥?,java,bouncycastle,openssh,ed25519,Java,Bouncycastle,Openssh,Ed25519,如何创建可用于SSH的OpenSSH ED25519私钥?目标是为OpenSSH客户机提供一个与.ssh/id_ed25519中相同格式的密钥文件 这是我当前的方法,它不会创建兼容的: import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.params.AsymmetricKeyParam

如何创建可用于SSH的OpenSSH ED25519私钥?目标是为OpenSSH客户机提供一个与
.ssh/id_ed25519
中相同格式的密钥文件

这是我当前的方法,它不会创建兼容的:

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;
    }
}
输出如下所示:

-----BEGIN OPENSSH PRIVATE KEY-----
MIIBTwIBAQQgA8BjYjSjUgM4PahSZQx3i9DWcEdGiGnBoA0tXCUENzKggeEwgd4C
AQEwKwYHKoZIzj0BAQIgf////////////////////////////////////////+0w
RAQgKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmEkUoUQEIHtCXtCXtCXtCXtC
XtCXtCXtCXtCXtCXtCYLXpx3EMhkBEEEKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqtJFogrhmhuKCGtOAe3Sx3SNFMkj1Nfm18YbIp6cWifs7T2QIgEAAAAAAA
AAAAAAAAAAAAABTe+d6i95zWWBJjGlz10+0CAQihRANCAARl0Kc+dO0Er1dpu6mh
/lZmTw3/DMKPLTzjosX2u7hQswV+U9o0WOYFd1JOqsGdkLfYuGmdZzWdk74dvV1O
+w5T
-----END OPENSSH PRIVATE KEY-----
    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
。。但不幸的是,SSH不接受它。

这样使用“Curve25519”将以X9使用的简短Weierstrass形式提供给您;这不适用于Ed25519,其定义为使用扭曲的Edwards形式。此外,通过JCA的不幸的
ECParameterSpec
类将其填充,可以得到原始的X9定义的“显式”表示,该表示现在已经过时,甚至几乎从未用于使用Weierstrass曲线的算法。因此,您创建的数据对于PEM类型的OPENSSH私钥是不正确的;它对OpenSSL的“传统”PEM类型的EC私钥有效,并且OpenSSL(虽然不喜欢,但仍然支持“param_enc explicit”)能够读取该类型的内容,尽管它不能用于与其他任何内容进行互操作

您需要将JCA与算法“ED25519”(而不是“EC”、“ECDSA”、“ECDH”和“ECMQV”等一起使用,这些都是X9/SECG),如下所示:

-----BEGIN OPENSSH PRIVATE KEY-----
MIIBTwIBAQQgA8BjYjSjUgM4PahSZQx3i9DWcEdGiGnBoA0tXCUENzKggeEwgd4C
AQEwKwYHKoZIzj0BAQIgf////////////////////////////////////////+0w
RAQgKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmEkUoUQEIHtCXtCXtCXtCXtC
XtCXtCXtCXtCXtCXtCYLXpx3EMhkBEEEKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqtJFogrhmhuKCGtOAe3Sx3SNFMkj1Nfm18YbIp6cWifs7T2QIgEAAAAAAA
AAAAAAAAAAAAABTe+d6i95zWWBJjGlz10+0CAQihRANCAARl0Kc+dO0Er1dpu6mh
/lZmTw3/DMKPLTzjosX2u7hQswV+U9o0WOYFd1JOqsGdkLfYuGmdZzWdk74dvV1O
+w5T
-----END OPENSSH PRIVATE KEY-----
    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
或者,由于您已经依赖Bouncy,请使用轻量级API:

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

这暗示了一个解决方案,但在Kotlin和公钥中,这是一个略微不同的问题: