Кодирование открытого ключа Ed25519 в формат SSH на Java
Начнем с того, что я новичок в криптографии и различных типах ключей/кодировок/форматов, поэтому поправьте меня, если я где-то ошибаюсь. У меня есть Java-приложение, которому необходимо сохранить ключ Ed25519 в хранилище ключей. Приложение является устаревшим, и некоторые методы и библиотеки не могут быть изменены. Он использует Apache MINA SSHD и BouncyCastle для хранения ключей в хранилище ключей, а также кодирования открытых ключей в формат SSH. С ключами RSA и DSA проблем нет. Проблема в том, что Apache MINA использует реализацию ключей EdDSA net.i2p, а BouncyCastle использует свои собственные ключи BCEdDSA. Метод, который мы используем, взят из MINA и возвращает ключ net.i2p для того случая, когда у меня возникли проблемы с сохранением его в хранилище ключей, поскольку JCAContentSigner BouncyCastle не смог распознать эту реализацию ключа. Проблема возникает при кодировании открытых ключей.
Я создал класс, реализующий ContentSigner, и использую класс EdDSAEngine net.i2p для создания подписывающего лица.
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. Я пробовал разные способы его кодирования: от вызова метода .getEncoded() самого PublicKey до использования ASN1InputStream от BouncyCastle, который является моим последним методом. Я также предоставлю метод кодирования ключей RSA, который возвращает ту же кодировку, что и инструмент ssh-keygen. Я хочу иметь возможность использовать открытые ключи для подключения к серверу с помощью шпатлевки. Любая помощь/предложения будут оценены по достоинству.
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());
}
}