如何使用 Bouncy Castle 创建兼容 OpenSSH 的 ED25519 密钥?
How to create an OpenSSH compatible ED25519 key with Bouncy Castle?
如何创建可用于 SSH 的 OpenSSH ED25519 私钥?目标是让密钥文件的格式与您在 .ssh/id_ed25519
中为您的 OpenSSH 客户端所使用的格式相同。
这是我目前的方法,不会创建兼容:
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-----
..但不幸的是不被 SSH 接受。
以这种方式使用“Curve25519”以 X9 使用的 short Weierstrass 形式提供给您;这不适用于 Ed25519,它被定义为使用 twisted-Edwards 形式。此外,通过 JCA 的不幸 ECParameterSpec
class 填充它给出了原始的 X9 定义的 'explicit' 表示,现在已经过时并且几乎从未使用过 do[=24= 的算法] 使用 Weierstrass 曲线。因此,您创建的数据对于 PEM 类型 OPENSSH PRIVATE KEY 是不正确的;它对 OpenSSL 的 'traditional' PEM 类型 EC PRIVATE KEY 有效,并且 OpenSSL(它仍然支持,但不喜欢,“param_enc explicit”)能够使用 that 类型读取该内容,但它不能用于与其他任何内容进行互操作。
您需要将 JCA 与算法“ED25519”(不是“EC”“ECDSA”“ECDH”“ECMQV”等都是X9/SECG)一起使用像这样:
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
如何创建可用于 SSH 的 OpenSSH ED25519 私钥?目标是让密钥文件的格式与您在 .ssh/id_ed25519
中为您的 OpenSSH 客户端所使用的格式相同。
这是我目前的方法,不会创建兼容:
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-----
..但不幸的是不被 SSH 接受。
以这种方式使用“Curve25519”以 X9 使用的 short Weierstrass 形式提供给您;这不适用于 Ed25519,它被定义为使用 twisted-Edwards 形式。此外,通过 JCA 的不幸 ECParameterSpec
class 填充它给出了原始的 X9 定义的 'explicit' 表示,现在已经过时并且几乎从未使用过 do[=24= 的算法] 使用 Weierstrass 曲线。因此,您创建的数据对于 PEM 类型 OPENSSH PRIVATE KEY 是不正确的;它对 OpenSSL 的 'traditional' PEM 类型 EC PRIVATE KEY 有效,并且 OpenSSL(它仍然支持,但不喜欢,“param_enc explicit”)能够使用 that 类型读取该内容,但它不能用于与其他任何内容进行互操作。
您需要将 JCA 与算法“ED25519”(不是“EC”“ECDSA”“ECDH”“ECMQV”等都是X9/SECG)一起使用像这样:
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