Недействительный 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));
Вы читаете файл с именем
.pem
и де-base64 все это и обработать результат как PKCS8-незашифрованный, по-видимому, успешно. Это означает, что файл НЕ был PEM-форматом. Формат PEM как минимум ДОЛЖЕН иметь действительные строки dash-BEGIN и dash-END, которые, если их не удалить, приводят к тому, что de-base64 либо дает сбой, либо ошибается. (Некоторые форматы PEM также имеют заголовки в стиле 822, которые должны обрабатываться.)Вы, кажется, используете BouncyCastle, но в моих версиях нет
PKCS8Generator
конструктор, который принимает толькоRSAPrivateKey
, Самое близкое, что работает, этоJcaPKCS8Generator (RSAPrivateKey implements PrivateKey, OutputEncryptor=null)
(т.е. другой, но связанный класс, и два аргумента, а не один).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
- Нажмите «Загрузить» и выберите приватный ключ
3) нажмите сейчас на «Конверсии» -> и выберите первый вариант «Экспортировать ключ OPENSSH»
- сохраните его как файл (не нужно никакого формата) и используйте его
Попробуйте использовать эту зависимость в своем 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-----