背景介绍 链接到标题
在使用 jsch-0.1.54 连接 sftp 服务器时,出现如下异常:
com.jcraft.jsch.JSchException: Session.connect: java.io.IOException: End of IO Stream Read
at com.jcraft.jsch.Session.connect(Session.java:565)
at com.jcraft.jsch.Session.connect(Session.java:183)
网上搜索一番后,大概确定可能是客户端与服务端使用的 SSH2 协议不兼容,导致连接失败。
过程分析 链接到标题
根据网上相关资料,先尝试升级 jsch 版本到 0.1.55,但问题仍然存在且相同。
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
通过打断点调试,了解到 jsch 内部有调试日志的输出机制,于是开启调试日志:
// 设置jsch日志输出到文件
JSch.setLogger(new com.jcraft.jsch.Logger() {
Path path = Paths.get("logs/jsch.log");
@Override
public boolean isEnabled(int level){
return true;
}
public void log(int level, String message){
try {
StandardOpenOption option =
!Files.exists(path) ?
StandardOpenOption.CREATE : StandardOpenOption.APPEND;
Files.write(path, java.util.Arrays.asList(message), option);
} catch (IOException e) {
System.err.println(message);
}
}
});
通过 jsch 日志,发现在 ssh 连接时密钥交换初始化阶段出现阻塞,客户端未收到服务端的响应:
Connecting to 192.168.1.11 port 22
Connection established
Remote version string: SSH-2.0-Serv-U_15.3.2.155
Local version string: SSH-2.0-JSCH-0.1.54
CheckCiphers: aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-ctr,arcfour,arcfour128,arcfour256
CheckKexes: diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
CheckSignatures: ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521
SSH_MSG_KEXINIT sent
Disconnecting from 192.168.1.11 port 22
在网上进一步搜索"SSH_MSG_KEXINIT"相关的资料,找到一篇关键资料JSch - No progress after SSH_MSG_KEXINIT is sent。根据该文的描述,这是官方 jsch 库的 bug,jsch 未遵循 SSH2.0 协议最新的 RFC 规范,在发送协议版本标识符给服务端时,不是以回车+换行(CR-LF)结尾,只送了换行符(LF),导致服务端认为未接收到完整的报文处于一直等待中。
根据该文中的指引,找到 mwiede 维护的 jsch 分支Fork of JSch-0.1.55,切换到新的 jsch 版本,再次尝试:
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>0.2.18</version>
</dependency>
不幸,仍没成功,但报了新的错误:
从jsch日志分析可知,是上图中标红的"host key algorithms" 未匹配导致协商失败,服务端默认仅启用了"ssh-rsa"算法,但从 mwiede 的 github 仓库说明中可知,“ssh-rsa"不太安全,在这个版本中默认禁用了,如果要开启,可如下操作:
// 启用RSA密钥,虽然不太安全,但有些sftp服务器只支持RSA
session.setConfig("server_host_key", session.getConfig("server_host_key") + ",ssh-rsa");
session.setConfig("PubkeyAcceptedAlgorithms", session.getConfig("PubkeyAcceptedAlgorithms") + ",ssh-rsa");
再次尝试,成功连接。
总结 链接到标题
以上方案均是从 sftp 客户端角度解决遇到的问题。但实际上从 sftp 服务端通过调整相关配置,也可以解决问题。参考SFTP connection not established for legacy Java clients,服务端可做如下配置:
- 启用:允许非 RFC 兼容的 SSH 协议版本交换(Allow non-RFC compliant SSH protocol version exchange)
- 配置更多的密钥算法(host key algorithms),例如,rsa-sha2-512,rsa-sha2-256 等。