Недействительный privateKey при использовании JGit и JSCH

Я использую следующий код для работы с Git в Java-приложении. У меня есть действительный ключ (используйте его все время), и этот конкретный код раньше работал для меня с тем же ключом и git-репозиторием, но теперь я получаю следующее исключение: недействительный privatekey: [B@59c40796.

    String remoteURL = "ssh://git@<git_repository>";
    TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback();
    File gitFolder = new File(workingDirectory);
    if (gitFolder.exists()) FileUtils.delete(gitFolder, FileUtils.RECURSIVE);

    Git git = Git.cloneRepository()
            .setURI(remoteURL)
            .setTransportConfigCallback(transportConfigCallback)
            .setDirectory(new File(workingDirectory))
            .call();
}


private static class SshTransportConfigCallback implements TransportConfigCallback {
    private final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
        @Override
        protected void configure(OpenSshConfig.Host hc, Session session) {
            session.setConfig("StrictHostKeyChecking", "no");
        }

        @Override
        protected JSch createDefaultJSch(FS fs) throws JSchException {
            JSch jSch = super.createDefaultJSch(fs);
            jSch.addIdentity("<key_path>/private_key.pem");

            return jSch;
        }
    };

После поиска в Интернете я изменил createDefaultJSch на использование pemWriter:

        @Override
        protected JSch createDefaultJSch(FS fs) throws JSchException {
            JSch jSch = super.createDefaultJSch(fs);
            byte[] privateKeyPEM = null;

            try {
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");

                List<String> lines = Files.readAllLines(Paths.get("<my_key>.pem"), StandardCharsets.US_ASCII);
                PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(String.join("", lines)));
                RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);

                PKCS8Generator pkcs8 = new PKCS8Generator(privKey);

                StringWriter writer = new StringWriter();
                PemWriter pemWriter = new PemWriter(writer);
                pemWriter.writeObject(pkcs8);

                privateKeyPEM = writer.toString().getBytes("US-ASCII");

            } catch (Exception e) {
                e.printStackTrace();
            }

            jSch.addIdentity("git", privateKeyPEM, null, null);

            return jSch;
        }

Но все равно получаю недопустимое исключение privateKey.

12 ответов

Последние версии OpenSSH (7.8 и новее) по умолчанию генерируют ключи в новом формате OpenSSH, который начинается с:

-----BEGIN OPENSSH PRIVATE KEY-----

JSch не поддерживает этот формат ключа.


Ты можешь использовать ssh-keygen преобразовать ключ в классический формат OpenSSH:

ssh-keygen -p -f file -m pem -P passphrase -N passphrase

(если ключ не зашифрован парольной фразой, используйте "" вместо passphrase)


Если вы используете Windows, вы можете использовать PuTTYgen (из пакета PuTTY). Загрузите ключ и перейдите в " Преобразования"> "Экспорт ключа OpenSSH". Для ключей RSA будет использоваться классический формат.


Если вы создаете новый ключ с ssh-keygen, просто добавь -m PEM чтобы сгенерировать новый ключ в классическом формате:

ssh-keygen -m PEM

Я тоже наткнулся на эту проблему. при запуске Jgit на Mac для некоторых пользователей мы видели следующее исключение:

org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:160)
    at org.eclipse.jgit.transport.SshTransport.getSession(SshTransport.java:137)
    at org.eclipse.jgit.transport.TransportGitSsh$SshFetchConnection.<init>(TransportGitSsh.java:274)
    at org.eclipse.jgit.transport.TransportGitSsh.openFetch(TransportGitSsh.java:169)
    at org.eclipse.jgit.transport.FetchProcess.executeImp(FetchProcess.java:136)
    at org.eclipse.jgit.transport.FetchProcess.execute(FetchProcess.java:122)
    at org.eclipse.jgit.transport.Transport.fetch(Transport.java:1236)
    at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:234)
    ... 17 more
Caused by: com.jcraft.jsch.JSchException: invalid privatekey: [B@e4487af
    at com.jcraft.jsch.KeyPair.load(KeyPair.java:664)
    at com.jcraft.jsch.KeyPair.load(KeyPair.java:561)
    at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:40)
    at com.jcraft.jsch.JSch.addIdentity(JSch.java:407)
    at com.jcraft.jsch.JSch.addIdentity(JSch.java:367)
    at org.eclipse.jgit.transport.JschConfigSessionFactory.getJSch(JschConfigSessionFactory.java:276)
    at org.eclipse.jgit.transport.JschConfigSessionFactory.createSession(JschConfigSessionFactory.java:220)
    at org.eclipse.jgit.transport.JschConfigSessionFactory.createSession(JschConfigSessionFactory.java:176)
    at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:110)

Основной причиной было обнаружено несоответствие закрытого ключа ssh. Исключение произошло только для пользователей с ключом более нового вида ed25519, который выводит этот заголовок ключа:

-----BEGIN OPENSSH PRIVATE KEY-----

вместо доброго RSA:

-----BEGIN RSA PRIVATE KEY-----

восстановление ключа RSA (ssh-keygen -t rsa), сделал исключение уйти.

Отредактируйте следующие комментарии: Если у вас OpenSSH 7.8 и выше, вам может понадобиться добавить -m PEM к команде генерации:ssh-keygen -t rsa -m PEM

Вместо преобразования формата ключа в формат, который поддерживает исходный JSch, вы также можете переключиться на форк JSch, который вы можете найти на https://github.com/mwiede/jsch .

Вам нужно только заменить свои координаты JSch Maven на com.github.mwiede:jsch:0.1.61.

Вилка поддерживает OPENSSHформат ключа и еще несколько алгоритмов, которые могут стать важными в будущем, поскольку серверы OpenSSH будут ограничивать разрешенные наборы алгоритмов наиболее безопасными.

Довольно поздно отвечать, но я хочу оставить след, чтобы решить эту проблему.

Дело, как уже упоминалось, в том, как вы генерируете ключ и -m PEM вариант разрешается.

Однако, если, как это случилось со мной, вы не смогли повторно сгенерировать ключ, поскольку публичная часть уже была установлена ​​на нескольких серверах, вы все равно можете преобразовать свой закрытый ключ в подходящий формат.

Для этого просто введите следующую команду:

ssh-keygen -p -m pem -f id_rsa

Он попросит ввести новую кодовую фразу. С параметрами -P (старая кодовая фраза) и -N (новая кодовая фраза) при необходимости вы можете предоставить их сразу.

JSch не поддерживает этот ключевой формат. Он поддерживает только RSAPrivateKey. Эта команда у меня работает. Попробуйте это решение

ssh-keygen -m PEM -t rsa -b 2048

// редактируется в RSA с размером ключа 2048

Поскольку ACH больше не поддерживается и не поддерживает большинство новейших алгоритмов ключей OpenSSH, JGIT теперь превратился в Apache MINA SSHD для подключения SSH. Для этого JGIT также предоставляет артефакт org.eclipse.jgit.ssh.apache . Чтобы использовать это, все, что вам нужно сделать, это переключить зависимость с артефакта JGit jsch на артефакт JGit ssh.apache и установить новый экземпляр SshdSessionFactory (который является реализацией SshSessionFactory в Apache) в org.eclipse.jgit.transport.SshTransport.

Зависимость переключателя;

От :

      <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
</dependency>

К :

      <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
</dependency>

Установка SshdSessionFactory для закрытого ключа с парольной фразой;

      TransportCommand<T, C> transportCommand = <Any Transport command in JGIT>;
File sshDir = new File(FS.DETECTED.userHome(), File.separator+SSH_DIR);
      SshdSessionFactory sshSessionFactory = new SshdSessionFactoryBuilder().setPreferredAuthentications("publickey")
        .setHomeDirectory(FS.DETECTED.userHome()).setSshDirectory(sshDir)
        .setKeyPasswordProvider(cp -> new IdentityPasswordProvider(cp)
        {
          @Override
          protected char[] getPassword(URIish uri, String message)
          {
            return passphrase.toCharArray();
          }
        }).build(null);
 transportCommand.setTransportConfigCallback(transport -> ((SshTransport) transport).setSshSessionFactory(sshSessionFactory));
  1. Вы читаете файл с именем .pem и де-base64 все это и обработать результат как PKCS8-незашифрованный, по-видимому, успешно. Это означает, что файл НЕ был PEM-форматом. Формат PEM как минимум ДОЛЖЕН иметь действительные строки dash-BEGIN и dash-END, которые, если их не удалить, приводят к тому, что de-base64 либо дает сбой, либо ошибается. (Некоторые форматы PEM также имеют заголовки в стиле 822, которые должны обрабатываться.)

  2. Вы, кажется, используете BouncyCastle, но в моих версиях нет PKCS8Generator конструктор, который принимает только RSAPrivateKey, Самое близкое, что работает, это JcaPKCS8Generator (RSAPrivateKey implements PrivateKey, OutputEncryptor=null) (т.е. другой, но связанный класс, и два аргумента, а не один).

  3. PemWriter буферизован, и вы не сбросили его, прежде чем посмотреть на StringWriter, В следствии writer.toString().getBytes() является массивом пустой / нулевой длины, который JSch справедливо считает недействительным.

С фиксированными № 2 и № 3, используя мой ввод и вызов JSch напрямую, а не через JGit, меня устраивает.

Помимо проблем с форматом закрытого ключа, эта ошибка « JSchException: недопустимый закрытый ключ » также может возникать, когда вы:

  • передайте закрытый ключ как массив байтов, используя этот перегруженный метод из библиотеки JSch:public void addIdentity(String name, byte[] prvkey, byte[] pubkey, byte[] passphrase)
  • и запустите приложение из операционной системы UNIX. Например, если вы развертываете свое приложение в экземпляре Linux у выбранного вами облачного провайдера.

Причиной этого является утверждение из исходного кода JSch, классKeyPair, методparseHeaderв строке 803:if (buf[i] == 0x0d) {...}: https://github.com/is/jsch/blob/master/src/main/java/com/jcraft/jsch/KeyPair.java#L803

Из-за этого утверждения учитываются только символы новой строки, закодированные как\r(0x0D == 13) (применимо к Windows и MacOS ). Но UNIX использует\n(0x0A == 10). Кодировки объясняются в этой теме, например: каковы различия между символьными литералами '\n' и '\r' в Java?


Поэтому, если ваш закрытый ключ имеет правильную структуру, но вы запускаете приложение из Linux (или любой другой ОС UNIX), то массив байтов, соответствующий содержимому вашего закрытого ключа, будет отличаться в зависимости от операционной системы, из которой вы запускаете ваше приложение .

Это пример, когда массивы байтов имеют разное содержимое:

  • Когда приложение работает в Linux: [114, 115, 97, 10, 69, 110] => 10"\n"
  • Когда приложение работает в Windows: [114, 115, 97, 13, 10, 69, 110] => 13 10"\r\n"

На этом изображении показано различное содержимое массивов байтов, преобразованных обратно в строки , когда приложение запускается как файл WAR из Linux и Windows, полученное с помощью удаленной отладки. Я использовал последнюю доступную версию JSch: https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.55 .


Поэтому, если вы запускаете свое приложение из Linux, решением будет:

1. Получите InputStream из ваших закрытых и открытых ключей ( файлы должны быть добавлены в каталог «ресурсы» вашего проекта Spring):

      InputStream privateKeyInputStream = new ClassPathResource("private-key.ppk").getInputStream();
InputStream publicKeyInputStream = new ClassPathResource("public-key.ppk").getInputStream();

2. Преобразуйте InputStream в ByteArrays

      byte[] privateKeyAsByteArray = IOUtils.toByteArray(privateKeyInputStream);
byte[] publicKeyAsByteArray = IOUtils.toByteArray(publicKeyInputStream);

3. Исправьте кодировку, заменив байты 10 (0x0A) байтами 13 (0x0D) перед вызовом метода из JSch:

      for (int i = 0; i < privateKeyAsByteArray.length; i++) {
    if (privateKeyAsByteArray[i] == 10) {   // if current element is a 10 (\n) (UNIX)
        privateKeyAsByteArray[i] = 13;      // replace it with 13 (\r) (a byte that can be interpreted)
    }
}

for (int i = 0; i < publicKeyAsByteArray.length; i++) {
    if (publicKeyAsByteArray[i] == 10) {    // if current element is a 10 (\n) (UNIX)
        publicKeyAsByteArray[i] = 13;       // replace it with 13 (\r) (a byte that can be interpreted)
    }
}

4. ПозвонитеaddIdentityметод:

      jSch.addIdentity("private-key.ppk", privateKeyAsByteArray, publicKeyAsByteArray, passphraseAsString.getBytes());

Я думал, что этот сценарий может помочь тому, кто сталкивается с этой ошибкой, покаifоператор из исходного кода библиотеки JSch обновлен для поддержки также символов UNIX.

Со своей стороны я сталкиваюсь с той же проблемой. В моем хост-файле ниже есть эта конфигурация, которая заставляет jsch читать конфигурацию ssh по id_ed25519. Вам следует использовать id_rsa.

         Host *
     AddKeysToAgent yes
     IdentityFile /Users/xxx/.ssh/id_ed25519

Так что исправьте ту же проблему с помощью PuTTYgen

1) Откройте PuTTYgen

  1. Нажмите «Загрузить» и выберите приватный ключ

3) нажмите сейчас на «Конверсии» -> и выберите первый вариант «Экспортировать ключ OPENSSH»

  1. сохраните его как файл (не нужно никакого формата) и используйте его

Попробуйте использовать эту зависимость в своем maven pom.xml и убедитесь, что вы проверили, от какого класса JSH зависимость используется в вашем коде. Иногда выбирается более старая зависимость. Здесь, используя приведенную ниже зависимость, даже OPEN SSH KEY будет работать нормально.

      <dependency>
            <groupId>com.github.mwiede</groupId>
            <artifactId>jsch</artifactId>
            <version>0.2.9</version>
        </dependency>

Я хотел добавить, что для того, чтобы избежать приведенных ниже заголовков, вам нужно создать ключ с помощью

      -C "any-comment"

Заголовки, которые будут удалены из закрытого ключа:

      Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,3551DFC375229D5758289E8D366082FE

Только уход

       -----BEGIN RSA PRIVATE KEY-----
YOUR_KEY_HERE
-----END RSA PRIVATE KEY-----
Другие вопросы по тегам