Кодирование открытого ключа 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());
        }
    }

0 ответов

Другие вопросы по тегам