记一次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;
本文地址:https://blog.csdn.net/qq_27822907/article/details/112858146
下一篇: selenium-确认进入了预期页面