记一次jsch ssh登录失败 End of IO Stream Read 异常的处理

                                                                                   

问题

       环境: jdk1.8、jsch-0.1.54.jar

ssh登录日志报错:at com.jcraft.jsch.Session.connectcom.jcraft.jsch.JSchException: Session.connect: java.io.IOException: End of IO Stream Read,且目标机器是第三方的,不清楚服务器上ssh版本、以及ssh相关配置,所以只能在客户端做相关操作;

前言:

百度、google 了一遍,发现该报错基本都是说密钥算法交换导致的问题。我这边也根据百度上摘自https://blog.csdn.net/dhj15951908233/article/details/79036246的全部试了一遍还是不行

解决过程:

 找到报错相关源码 、报错确实是在发送密钥交换算法send_kexinit()后、没收到response而产生的。

   V_S=new byte[i]; System.arraycopy(buf.buffer, 0, V_S, 0, i);

      if(JSch.getLogger().isEnabled(Logger.INFO)){
        JSch.getLogger().log(Logger.INFO, 
                             "Remote version string: "+Util.byte2str(V_S));
        JSch.getLogger().log(Logger.INFO, 
                             "Local version string: "+Util.byte2str(V_C));
      }

      send_kexinit();//客户端发送支持的密钥算法

      buf=read(buf);
      if(buf.getCommand()!=SSH_MSG_KEXINIT){
        in_kex=false;
	throw new JSchException("invalid protocol: "+buf.getCommand());
      }

 

 

我用的jdk1.8和jsch-0.1.54.jar大部分算法都是支持的,为了确认支持算法是都支持服务器的,通过人工手动登录 ssh -v ip port ,查看日志

可以看出来kex是协商后的是diffie-hellman-group14-sha1、host key algorithm协商后的是ssh-rsa 、 ciper协商后的是hmac-sha1  compression:none

jsch支持的算法内容:

config.put("kex", "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1");
    config.put("server_host_key", "ssh-rsa,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521");
    config.put("cipher.s2c", 
               "aes128-ctr,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-ctr,aes192-cbc,aes256-ctr,aes256-cbc");
    config.put("cipher.c2s",
               "aes128-ctr,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-ctr,aes192-cbc,aes256-ctr,aes256-cbc");

    config.put("mac.s2c", "hmac-md5,hmac-sha1,hmac-sha2-256,hmac-sha1-96,hmac-md5-96");
    config.put("mac.c2s", "hmac-md5,hmac-sha1,hmac-sha2-256,hmac-sha1-96,hmac-md5-96");
    config.put("compression.s2c", "none");
    config.put("compression.c2s", "none");

    config.put("lang.s2c", "");
    config.put("lang.c2s", "");

    config.put("compression_level", "6");

    config.put("diffie-hellman-group-exchange-sha1", 
                                "com.jcraft.jsch.DHGEX");
    config.put("diffie-hellman-group1-sha1", 
	                        "com.jcraft.jsch.DHG1");
    config.put("diffie-hellman-group14-sha1", 
               "com.jcraft.jsch.DHG14");    // available since JDK8.
    config.put("diffie-hellman-group-exchange-sha256", 
               "com.jcraft.jsch.DHGEX256"); // available since JDK1.4.2.
                                            // On JDK8, 2048bits will be used.
    config.put("ecdsa-sha2-nistp256", "com.jcraft.jsch.jce.SignatureECDSA");
    config.put("ecdsa-sha2-nistp384", "com.jcraft.jsch.jce.SignatureECDSA");
    config.put("ecdsa-sha2-nistp521", "com.jcraft.jsch.jce.SignatureECDSA");

    config.put("ecdh-sha2-nistp256", "com.jcraft.jsch.DHEC256");
    config.put("ecdh-sha2-nistp384", "com.jcraft.jsch.DHEC384");
    config.put("ecdh-sha2-nistp521", "com.jcraft.jsch.DHEC521");

    config.put("ecdh-sha2-nistp", "com.jcraft.jsch.jce.ECDHN");

    config.put("dh",            "com.jcraft.jsch.jce.DH");
    config.put("3des-cbc",      "com.jcraft.jsch.jce.TripleDESCBC");
    config.put("blowfish-cbc",  "com.jcraft.jsch.jce.BlowfishCBC");
    config.put("hmac-sha1",     "com.jcraft.jsch.jce.HMACSHA1");
    config.put("hmac-sha1-96",  "com.jcraft.jsch.jce.HMACSHA196");
    config.put("hmac-sha2-256",  "com.jcraft.jsch.jce.HMACSHA256");
    // The "hmac-sha2-512" will require the key-length 2048 for DH,
    // but Sun's JCE has not allowed to use such a long key.
    //config.put("hmac-sha2-512",  "com.jcraft.jsch.jce.HMACSHA512");
    config.put("hmac-md5",      "com.jcraft.jsch.jce.HMACMD5");
    config.put("hmac-md5-96",   "com.jcraft.jsch.jce.HMACMD596");
    config.put("sha-1",         "com.jcraft.jsch.jce.SHA1");
    config.put("sha-256",         "com.jcraft.jsch.jce.SHA256");
    config.put("sha-384",         "com.jcraft.jsch.jce.SHA384");
    config.put("sha-512",         "com.jcraft.jsch.jce.SHA512");
    config.put("md5",           "com.jcraft.jsch.jce.MD5");
    config.put("signature.dss", "com.jcraft.jsch.jce.SignatureDSA");
    config.put("signature.rsa", "com.jcraft.jsch.jce.SignatureRSA");
    config.put("signature.ecdsa", "com.jcraft.jsch.jce.SignatureECDSA");
    config.put("keypairgen.dsa",   "com.jcraft.jsch.jce.KeyPairGenDSA");
    config.put("keypairgen.rsa",   "com.jcraft.jsch.jce.KeyPairGenRSA");
    config.put("keypairgen.ecdsa", "com.jcraft.jsch.jce.KeyPairGenECDSA");
    config.put("random",        "com.jcraft.jsch.jce.Random");

    config.put("none",           "com.jcraft.jsch.CipherNone");

    config.put("aes128-cbc",    "com.jcraft.jsch.jce.AES128CBC");
    config.put("aes192-cbc",    "com.jcraft.jsch.jce.AES192CBC");
    config.put("aes256-cbc",    "com.jcraft.jsch.jce.AES256CBC");

    config.put("aes128-ctr",    "com.jcraft.jsch.jce.AES128CTR");
    config.put("aes192-ctr",    "com.jcraft.jsch.jce.AES192CTR");
    config.put("aes256-ctr",    "com.jcraft.jsch.jce.AES256CTR");
    config.put("3des-ctr",      "com.jcraft.jsch.jce.TripleDESCTR");
    config.put("arcfour",      "com.jcraft.jsch.jce.ARCFOUR");
    config.put("arcfour128",      "com.jcraft.jsch.jce.ARCFOUR128");
    config.put("arcfour256",      "com.jcraft.jsch.jce.ARCFOUR256");

    config.put("userauth.none",    "com.jcraft.jsch.UserAuthNone");
    config.put("userauth.password",    "com.jcraft.jsch.UserAuthPassword");
    config.put("userauth.keyboard-interactive",    "com.jcraft.jsch.UserAuthKeyboardInteractive");
    config.put("userauth.publickey",    "com.jcraft.jsch.UserAuthPublicKey");
    config.put("userauth.gssapi-with-mic",    "com.jcraft.jsch.UserAuthGSSAPIWithMIC");
    config.put("gssapi-with-mic.krb5",    "com.jcraft.jsch.jgss.GSSContextKrb5");

    config.put("zlib",             "com.jcraft.jsch.jcraft.Compression");
    config.put("zlib@openssh.com", "com.jcraft.jsch.jcraft.Compression");

    config.put("pbkdf", "com.jcraft.jsch.jce.PBKDF");

    config.put("StrictHostKeyChecking",  "ask");
    config.put("HashKnownHosts",  "no");

    config.put("PreferredAuthentications", "gssapi-with-mic,publickey,keyboard-interactive,password");

    config.put("CheckCiphers", "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-ctr,arcfour,arcfour128,arcfour256");
    config.put("CheckKexes", "diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521");
    config.put("CheckSignatures", "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521");

    config.put("MaxAuthTries", "6");
    config.put("ClearAllForwardings", "no");
  }

可以看到手动ssh登录抓包如下图(tcpdump):也是一样的结果,而且jsch源码里面这些算法都是包含的

jsch登录抓包 如下图:发现发送的算法 都是有的,但是服务器就是没响应。

 

通过jsch登录抓包和手动登录的抓包分析看,发现只用在发送协议版本的时候不一样,分别是SSH-2.0-OpenSSH_8.1、SSH-2.0-JSCH-0.1.54,为了验证改协议版本影响,故通过代码 直接发送 十六进制流 :

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Date;

public class Client {

    public static void main(String[] args) {
        String ip=args[0] ;
        String port=args[1];
        String sshversion="";
        try {
             sshversion = args[2];
        }catch (Exception e){
//ssh版本十六进制流
            sshversion="5353482d322e302d4a5343482d302e312e35340a";
                        //5353482d322e302d4a5343482d302e312e3500
//                        5353482d322e302d4f70656e5353485f382e310d0a
                        //5353482d322e302d4a5343482d302e312e3534
//            5353482d322e302d4f70656e5353485f382e310d0a
        }
        Socket socket = null;
        try {
            System.out.println("connecting...");
            socket = createSocket(ip, Integer.parseInt(port),0);
            System.out.println("connection success");
//            String str = ""; //发送的16进制字符串
            byte[] bytes = hexStringToByteArray(sshversion);
            OutputStream os = socket.getOutputStream();
            InputStream in=socket.getInputStream();
            Long start1=System.currentTimeMillis();
            byte[] once=readStream(in);
            Long start2=System.currentTimeMillis();
            System.out.println(start2-start1);
            System.out.println(new String(once));
            System.out.println("ONCE 16 hex:::"+Arrays.toString(once).replace(",","").replace(" ",""));
            os.write(bytes);
            Thread.sleep(1000);
//send_kexinit 十六进制流
           // 000004fc041403425c20f5e040380f3f51b040069a8a0000010d637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c656364682d736861322d6e697374703235362c656364682d736861322d6e697374703338342c656364682d736861322d6e697374703532312c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f757031362d7368613531322c6469666669652d68656c6c6d616e2d67726f757031382d7368613531322c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6578742d696e666f2d63000001667273612d736861322d3531322d636572742d763031406f70656e7373682e636f6d2c7273612d736861322d3235362d636572742d763031406f70656e7373682e636f6d2c7373682d7273612d636572742d763031406f70656e7373682e636f6d2c7273612d736861322d3531322c7273612d736861322d3235362c7373682d7273612c65636473612d736861322d6e697374703235362d636572742d763031406f70656e7373682e636f6d2c65636473612d736861322d6e697374703338342d636572742d763031406f70656e7373682e636f6d2c65636473612d736861322d6e697374703532312d636572742d763031406f70656e7373682e636f6d2c7373682d656432353531392d636572742d763031406f70656e7373682e636f6d2c65636473612d736861322d6e697374703235362c65636473612d736861322d6e697374703338342c65636473612d736861322d6e697374703532312c7373682d65643235353139000000346165733132382d6374722c6165733139322d6374722c6165733235362d6374722c6165733132382d6362632c336465732d636263000000346165733132382d6374722c6165733139322d6374722c6165733235362d6374722c6165733132382d6362632c336465732d636263000000d5756d61632d36342d65746d406f70656e7373682e636f6d2c756d61632d3132382d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861312d65746d406f70656e7373682e636f6d2c756d61632d3634406f70656e7373682e636f6d2c756d61632d313238406f70656e7373682e636f6d2c686d61632d736861322d3235362c686d61632d736861322d3531322c686d61632d73686131000000d5756d61632d36342d65746d406f70656e7373682e636f6d2c756d61632d3132382d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861312d65746d406f70656e7373682e636f6d2c756d61632d3634406f70656e7373682e636f6d2c756d61632d313238406f70656e7373682e636f6d2c686d61632d736861322d3235362c686d61632d736861322d3531322c686d61632d736861310000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000000000000000000000000000000000
            os.write(hexStringToByteArray("000001ec0d14ccfb5b5539b5ae8a4e9bd3f958e4ea5a0000001b6469666669652d68656c6c6d616e2d67726f757031342d736861310000004b7373682d7273612c7373682d6473732c65636473612d736861322d6e697374703235362c65636473612d736861322d6e697374703338342c65636473612d736861322d6e69737470353231000000606165733132382d6374722c6165733132382d6362632c336465732d6374722c336465732d6362632c626c6f77666973682d6362632c6165733139322d6374722c6165733139322d6362632c6165733235362d6374722c6165733235362d636263000000606165733132382d6374722c6165733132382d6362632c336465732d6374722c336465732d6362632c626c6f77666973682d6362632c6165733139322d6374722c6165733139322d6362632c6165733235362d6374722c6165733235362d63626300000039686d61632d6d64352c686d61632d736861312c686d61632d736861322d3235362c686d61632d736861312d39362c686d61632d6d64352d393600000039686d61632d6d64352c686d61632d736861312c686d61632d736861322d3235362c686d61632d736861312d39362c686d61632d6d64352d3936000000046e6f6e65000000046e6f6e6500000000000000000000000000df4ed8e5522c1a1cb422c77ebb"));
            Long start3=System.currentTimeMillis();
            byte[] two=readStream(in);
            Long start4=System.currentTimeMillis();
            System.out.println(start4-start3);
            System.out.println("KEX INIT:"+new String(two));
            System.out.println("twiCE 16 hex:::"+Arrays.toString(two).replace(",","").replace(" ",""));
            os.close();
            Thread.sleep(1000*30);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e) {

                }
            }
        }
    }

    /**
     * 16进制表示的字符串转换为字节数组
     *
     * @param hexString 16进制表示的字符串
     * @return byte[] 字节数组
     */
    public static byte[] hexStringToByteArray(String hexString) {
        hexString = hexString.replaceAll(" ", "");
        int len = hexString.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                    .digit(hexString.charAt(i + 1), 16));
        }
        return bytes;
    }
    static Socket createSocket(String host, int port, int timeout) throws RuntimeException{
        Socket socket=null;
        if(timeout==0){
            try{
                socket=new Socket(host, port);
                return socket;
            }
            catch(Exception e){
                String message=e.toString();
                if(e instanceof Throwable)
                    throw new RuntimeException(message, (Throwable)e);
                throw new RuntimeException(message);
            }
        }
        final String _host=host;
        final int _port=port;
        final Socket[] sockp=new Socket[1];
        final Exception[] ee=new Exception[1];
        String message="";
        Thread tmp=new Thread(new Runnable(){
            public void run(){
                sockp[0]=null;
                try{
                    sockp[0]=new Socket(_host, _port);
                }
                catch(Exception e){
                    ee[0]=e;
                    if(sockp[0]!=null && sockp[0].isConnected()){
                        try{
                            sockp[0].close();
                        }
                        catch(Exception eee){}
                    }
                    sockp[0]=null;
                }
            }
        });
        tmp.setName("Opening Socket "+host);
        tmp.start();
        try{
            tmp.join(timeout);
            message="timeout: ";
        }
        catch(java.lang.InterruptedException eee){
        }
        if(sockp[0]!=null && sockp[0].isConnected()){
            socket=sockp[0];
        }
        else{
            message+="socket is not established";
            if(ee[0]!=null){
                message=ee[0].toString();
            }
            tmp.interrupt();
            tmp=null;
            throw new RuntimeException(message, ee[0]);
        }
        return socket;
    }

    /**
     * 递归读取流
     *
     * @param output
     * @param inStream
     * @return
     * @throws Exception
     */
    public static void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {
        long start = System.currentTimeMillis();
        while (inStream.available() == 0) {
            if ((System.currentTimeMillis() - start) > 5*60* 1000) {//超时退出
                throw new SocketTimeoutException("超时读取");
            }
        }
        byte[] buffer = new byte[2048];
        int read = inStream.read(buffer);
        System.out.println("***********数据"+read);
        output.write(buffer, 0, read);
        Thread.sleep(100);//需要延时以下,不然还是有概率漏读
        int a = inStream.available();//再判断一下,是否有可用字节数或者根据实际情况验证报文完整性
        if (a > 0) {
            readStreamWithRecursion(output, inStream);
        }
    }

    /**
     * 读取字节
     *
     * @param inStream
     * @return
     * @throws Exception
     */
    private  static byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        readStreamWithRecursion(output, inStream);
        output.close();
        int size = output.size();
        return output.toByteArray();
    }

    static byte[] str2byte(String str, String encoding){
        if(str==null)
            return null;
        try{ return str.getBytes(encoding); }
        catch(java.io.UnsupportedEncodingException e){
            return str.getBytes();
        }
    }

    static byte[] str2byte(String str){
        return str2byte(str, "UTF-8");
    }
    /**
     * 字节数组转16进制
     * @param bytes 需要转换的byte数组
     * @return  转换后的Hex字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if(hex.length() < 2){
                sb.append(0);
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

当发送SSH-2.0-JSCH-0.1.54 十六进制流和 手动ssh登录的key exchange init 的十六进制流 服务器也是 无响应的;

当发送SSH-2.0-OpenSSH_8.1十六进制流和jsch登录的key exchange init  的十六进制流 服务器是 有响应的;

根据测试验证了该问题解决方案的可行性再次进行测试,登录成功。

总结:

最终问题是解决了,通过修改源码的jsch版本 为SSH-2.0-OpenSSH_8.1;

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值