背景介绍 链接到标题

在使用 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报错1-ssh-rsa

从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 等。

参考资料 链接到标题