提问人:Atanas Donev 提问时间:11/10/2023 更新时间:11/11/2023 访问量:67
在 Java 中将 Ed25519 公钥编码为 SSH 格式
Encoding a Ed25519 Public Key to SSH format in Java
问:
首先,我是密码学和不同类型的密钥/编码/格式的新手,所以如果我在某个地方错了,请纠正我。 我有一个 java 应用程序,需要将 Ed25519 密钥保存到密钥库。该应用程序是旧版应用程序,某些方法和库无法更改。它使用 Apache MINA SSHD 和 BouncyCastle 将密钥存储在密钥库中,并将公钥编码为 SSH 格式。RSA 和 DSA 密钥没有问题。问题在于 Apache MINA 使用 EdDSA 密钥的 net.i2p 实现,而 BouncyCastle 使用自己的 BCEdDSA 密钥。我们使用的方法来自 MINA 并返回 witch 的 net.i2p 密钥 我无法将其保存到密钥库,因为 BouncyCastle 的 JCAContentSigner 无法识别密钥的这种实现。在对公钥进行编码时,问题就出现了。
我创建了一个实现 ContentSigner 的类,并使用 net.i2p 的 EdDSAEngine 类来创建签名者。
private class EdDSAContentSigner implements ContentSigner
{
private final AlgorithmIdentifier sigAlgId;
private final PrivateKey privateKey;
private ByteArrayOutputStream stream;
public EdDSAContentSigner(AlgorithmIdentifier sigAlgId, PrivateKey privKey)
{
this.sigAlgId = sigAlgId;
this.privateKey = privKey;
this.stream = new ByteArrayOutputStream();
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier()
{
return sigAlgId;
}
@Override
public OutputStream getOutputStream()
{
stream.reset();
return stream;
}
@Override
public byte[] getSignature()
{
byte[] dataToSign = stream.toByteArray();
try
{
EdDSAEngine sig = new EdDSAEngine();
sig.initSign(privateKey);
return sig.signOneShot(dataToSign);
}
catch (GeneralSecurityException e)
{
LOG.error("Cannot sign data : " + e.getMessage(), e);
throw new IllegalStateException("Cannot sign data : " + e.getMessage(), e);
}
}
}
然后我设法创建了一个证书并将密钥保存到密钥库。 该应用程序具有上传私钥文件的功能,它以 SSH 格式生成公钥 - 从 ssh-ed25519 开始。
现在我在将公钥编码为 SSH 格式时遇到了问题。即使我使用相同的私钥文件,它也总是与 ssh-keygen 工具生成的不同。同样,公钥可以是不同类型的 net.i2p/BouncyCastle/sun.security.ec.ed.EdDSAPublicKeyImpl,具体取决于调用我的 encodeEdDSAPublicKey 方法的方法。我尝试了不同的方法来编码它,从在 PublicKey 本身上调用 .getEncoded() 方法到使用 BouncyCastle 的 ASN1InputStream,这是我的最新方法。我还将提供 RSA 密钥编码方法 witch 返回与 ssh-keygen 工具相同的编码。我希望能够使用公钥通过 putty 连接到服务器。任何帮助/建议将不胜感激。
private static String encodeEdDSAPublicKey(PublicKey publicKey)
throws IOException
{
try(ASN1InputStream asn1InputStream = new ASN1InputStream(publicKey.getEncoded()))
{
ASN1Primitive primitive = asn1InputStream.readObject();
byte[] keyBytes ((ASN1Sequence)primitive).getObjectAt(1).toASN1Primitive().getEncoded();
ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(byteOs);
dos.writeInt("ssh-ed25519".getBytes().length);
dos.write("ssh-ed25519".getBytes());
dos.writeInt(keyBytes.length);
dos.write(keyBytes);
return Base64.getEncoder().encodeToString(byteOs.toByteArray());
}
}
private static String encodeRSAPublicKey(PublicKey publicKey)
throws IOException
{
String publicKeyEncoded;
RSAPublicKey rsaPublicKey = (RSAPublicKey)publicKey;
ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(byteOs);
dos.writeInt("ssh-rsa".getBytes().length);
dos.write("ssh-rsa".getBytes());
dos.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
dos.write(rsaPublicKey.getPublicExponent().toByteArray());
dos.writeInt(rsaPublicKey.getModulus().toByteArray().length);
dos.write(rsaPublicKey.getModulus().toByteArray());
publicKeyEncoded = Base64.getEncoder().encodeToString(byteOs.toByteArray());
return publicKeyEncoded;
}
这是我根据算法选择正确编码方法的方法
public static String encodePublicKey(PublicKey publicKey, String name)
throws IOException
{
String suffix = "";
String algorithm = publicKey.getAlgorithm();
if (name != null)
{
suffix = name);
}
switch (algorithm)
{
case "RSA":
return "ssh-rsa " + encodeRSAPublicKey(publicKey) + suffix;
case "DSA":
return "ssh-dss " + encodeDSAPublicKey(publicKey) + suffix;
case "Ed25519":
case "EdDSA":
return "ssh-ed25519 " + encodeEdDSAPublicKey(publicKey) + suffix;
default:
throw new IOException("Unknown public key encoding: " + publicKey.getAlgorithm());
}
}
答:
你离得很近。Java 是一个 SPKI 结构,其字段 1 是一个 BIT STRING,其中包含特定于算法的数据,对于 EdDSA 来说,这些数据是原始点编码。用:PublicKey.getEncoded()
ASN1Object spki = new ASN1InputStream(pubkey.getEncoded()) .readObject();
// or wrapped in the try as you have it is slightly cleaner
byte[] point = ((ASN1BitString) ((ASN1Sequence)spki).getObjectAt(1) ).getOctets();
或者使用特定于类型的 Bouncy 类来为您处理解析和类型转换:
byte[] point = SubjectPublicKeyInfo.getInstance(pubkey.getEncoded()).getPublicKeyData().getOctets();
或者,如果使用 Bouncy 提供程序(和键类),更简单地使用:
byte[] point = ((org.bouncycastle.jcajce.interfaces.EdDSAPublicKey)pubkey).getPointEncoding();
// or org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey
无论如何,将长度和内容写入SSH格式,就像您已经拥有的那样。point
评论