197

Does anyone have an example of an SSH library connection using Java.

rperez
  • 8,430
  • 11
  • 36
  • 44
  • I used to use Trilead SSH but when I checked the site today it seems they are giving up on it. :( It was my absolute favorite one. – Peter D Jun 15 '09 at 13:42
  • 1
    BTW, it appears that Trilead SSH2 is being actively maintained (in Oct 2013): [https://github.com/jenkinsci/trilead-ssh2] – Mike Godin Oct 10 '13 at 03:00
  • Trilead SSH2 has a fork at https://github.com/connectbot/sshlib – user7610 Aug 16 '19 at 11:50

7 Answers7

130

The Java Secure Channel (JSCH) is a very popular library, used by maven, ant and eclipse. It is open source with a BSD style license.

David Rabinowitz
  • 29,904
  • 14
  • 93
  • 125
  • 2
    You eed to download the source from https://sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/download and run "ant javadoc" – David Rabinowitz Oct 30 '09 at 10:56
  • 77
    I've tried using JSch some time ago and can not understand how it got so popular. It offers absolutely no documentation (not even within the source) and a horrible API design (http://techtavern.wordpress.com/2008/09/30/about-jsch-open-source-project/ sums it up quite well) – rluba Sep 25 '10 at 10:15
  • 15
    Yes Jsch is awful, this is better: https://github.com/shikhar/sshj – anio Mar 30 '11 at 13:33
  • 3
    A variant of JSch with javadoc for the public methods: https://github.com/ePaul/jsch-documentation – user423430 Jan 19 '12 at 22:30
  • but jsch doesn't support dynamic socket forwarding:http://www.java-forums.org/new-java/62093-ssh-library-supports-dynamic-port-forwarding-anyone-knows-any.html – cleverpig Dec 05 '12 at 03:53
  • but any other client doesn't support `gssapi-with-mic` authentication :( – Dan Oct 25 '13 at 16:09
  • 4
    http://stackoverflow.com/questions/2405885/any-good-jsch-examples/11902536#11902536 contains an example for using JSCH to run commands and get the output. – Charity Leschinski Jan 01 '14 at 02:24
  • @DavidRabinowitz How can we run commands such as "sqlplus / as sysdba" or "lsnrctl status"? They dont come with an output when i run these. – AloneInTheDark Feb 07 '14 at 15:29
  • I'm having a bad experience trying to keep SFTP sessions alive. I don't know if others are better. BTW, Spring's SFTP API uses JSCH internally. – Sridhar Sarnobat Jun 17 '15 at 01:24
  • Here are some examples. But they are done in swing. http://www.jcraft.com/jsch/examples/ – cevaris Nov 30 '15 at 00:18
  • 1
    Here is usable API wrapper for JSch entrails http://stackoverflow.com/a/37067557/448078 – Mike May 06 '16 at 15:09
  • User Spring SftpRemoteFileTemplate lib. it has the best API – Vahe Harutyunyan Jul 12 '21 at 20:58
69

Update: The GSOC project and the code there isn't active, but this is: https://github.com/hierynomus/sshj

hierynomus took over as maintainer since early 2015. Here is the older, no longer maintained, Github link:

https://github.com/shikhar/sshj


There was a GSOC project:

http://code.google.com/p/commons-net-ssh/

Code quality seem to be better than JSch, which, while a complete and working implementation, lacks documentation. Project page spots an upcoming beta release, last commit to the repository was mid-august.

Compare the APIs:

http://code.google.com/p/commons-net-ssh/

    SSHClient ssh = new SSHClient();
    //ssh.useCompression(); 
    ssh.loadKnownHosts();
    ssh.connect("localhost");
    try {
        ssh.authPublickey(System.getProperty("user.name"));
        new SCPDownloadClient(ssh).copy("ten", "/tmp");
    } finally {
        ssh.disconnect();
    }

http://www.jcraft.com/jsch/

Session session = null;
Channel channel = null;

try {

JSch jsch = new JSch();
session = jsch.getSession(username, host, 22);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
session.connect();

// exec 'scp -f rfile' remotely
String command = "scp -f " + remoteFilename;
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);

// get I/O streams for remote scp
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();

channel.connect();

byte[] buf = new byte[1024];

// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();

while (true) {
    int c = checkAck(in);
    if (c != 'C') {
        break;
    }

    // read '0644 '
    in.read(buf, 0, 5);

    long filesize = 0L;
    while (true) {
        if (in.read(buf, 0, 1) < 0) {
            // error
            break;
        }
        if (buf[0] == ' ') {
            break;
        }
        filesize = filesize * 10L + (long) (buf[0] - '0');
    }

    String file = null;
    for (int i = 0;; i++) {
        in.read(buf, i, 1);
        if (buf[i] == (byte) 0x0a) {
            file = new String(buf, 0, i);
            break;
        }
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    // read a content of lfile
    FileOutputStream fos = null;

    fos = new FileOutputStream(localFilename);
    int foo;
    while (true) {
        if (buf.length < filesize) {
            foo = buf.length;
        } else {
            foo = (int) filesize;
        }
        foo = in.read(buf, 0, foo);
        if (foo < 0) {
            // error
            break;
        }
        fos.write(buf, 0, foo);
        filesize -= foo;
        if (filesize == 0L) {
            break;
        }
    }
    fos.close();
    fos = null;

    if (checkAck(in) != 0) {
        System.exit(0);
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    channel.disconnect();
    session.disconnect();
}

} catch (JSchException jsche) {
    System.err.println(jsche.getLocalizedMessage());
} catch (IOException ioe) {
    System.err.println(ioe.getLocalizedMessage());
} finally {
    channel.disconnect();
    session.disconnect();
}

}
Jason
  • 11,709
  • 9
  • 66
  • 82
miku
  • 181,842
  • 47
  • 306
  • 310
  • 2
    Thanks! I used Apache SSHD code (which offers an async API) as seed which gave the project a kickstart. – shikhar Nov 20 '09 at 22:31
  • 1
    Great. I started a project using JSch, but I really like to switch, if I hear more positive feedback about commons-net-ssh. – miku Nov 21 '09 at 15:07
  • 6
    I should have mentioned that I was the GSOC student :) – shikhar Nov 22 '09 at 12:34
  • 2
    happy to announce http://github.com/shikhar/sshj - you can find jar's there, figuring out how to get it on a maven repo – shikhar Mar 08 '10 at 20:30
  • 1
    jsch's SFTP is much simpler than what's given here. Perhaps this is old code, but it certainly isn't an example of the modern API. – Charles Duffy May 21 '13 at 18:55
  • Also -- to the best of my knowledge, jsch is the _ONLY_ SSH implementation for Java with any kind of SSH agent support available. – Charles Duffy May 21 '13 at 18:55
28

I just discovered sshj, which seems to have a much more concise API than JSCH (but it requires Java 6). The documentation is mostly by examples-in-the-repo at this point, and usually that's enough for me to look elsewhere, but it seems good enough for me to give it a shot on a project I just started.

Ed Brannin
  • 7,691
  • 2
  • 28
  • 32
  • 4
    SSHJ is actually workable by someone from the outside world. JSCH is a mess of bad documentation and API design with hidden and largely indecipherable dependencies. Unless you want to spend a lot of time walking through code to try to figure out what's up, use SSHJ. (And I wish I was being harsh or facetious about JSCH. I really do.) – Robert Fischer Jan 15 '14 at 13:41
  • 2
    Yes, sshj. Everything I tried with it worked: SCP, remote process execution, local and remote port forwarding, agent proxy with [jsch-agent-proxy](http://www.jcraft.com/jsch-agent-proxy). JSCH was a mess. – Laurent Caillette May 12 '17 at 14:29
  • 1
    The problem with SSHJ is that it's very hard to execute multiple commands. SSHJ may be great for fire-and-forget of commands, but it's a pain if you want to program more complicated interaction. (I just wasted half a day on it) – bvdb Aug 30 '18 at 21:00
19

Take a look at the very recently released SSHD, which is based on the Apache MINA project.

T-Gergely
  • 472
  • 5
  • 13
mshafrir
  • 5,190
  • 12
  • 43
  • 56
5

There is a brand new version of Jsch up on github: https://github.com/vngx/vngx-jsch Some of the improvements include: comprehensive javadoc, enhanced performance, improved exception handling, and better RFC spec adherence. If you wish to contribute in any way please open an issue or send a pull request.

Scott
  • 1,012
  • 7
  • 14
0

http://code.google.com/p/connectbot/, Compile src\com\trilead\ssh2 on windows linux or android , it can create Local Port Forwarder or create Dynamic Port Forwarder or other else

Adam Wagner
  • 15,469
  • 7
  • 52
  • 66
a1241312
  • 1
  • 1
0

I took miku's answer and jsch example code. I then had to download multiple files during the session and preserve original timestamps. This is my example code how to do it, probably many people find it usefull. Please ignore filenameHack() function its my own usecase.

package examples;

import com.jcraft.jsch.*;
import java.io.*;
import java.util.*;

public class ScpFrom2 {

    public static void main(String[] args) throws Exception {
        Map<String,String> params = parseParams(args);
        if (params.isEmpty()) {
            System.err.println("usage: java ScpFrom2 "
                    + " user=myid password=mypwd"
                    + " host=myhost.com port=22"
                    + " encoding=<ISO-8859-1,UTF-8,...>"
                    + " \"remotefile1=/some/file.png\""
                    + " \"localfile1=file.png\""
                    + " \"remotefile2=/other/file.txt\""
                    + " \"localfile2=file.txt\""

            );
            return;
        }

        // default values
        if (params.get("port") == null)
            params.put("port", "22");
        if (params.get("encoding") == null)
            params.put("encoding", "ISO-8859-1"); //"UTF-8"

        Session session = null;
        try {
            JSch jsch=new JSch();
            session=jsch.getSession(
                    params.get("user"),  // myuserid
                    params.get("host"),  // my.server.com
                    Integer.parseInt(params.get("port")) // 22
            );
            session.setPassword( params.get("password") );
            session.setConfig("StrictHostKeyChecking", "no"); // do not prompt for server signature

            session.connect();

            // this is exec command and string reply encoding
            String encoding = params.get("encoding");

            int fileIdx=0;
            while(true) {
                fileIdx++;

                String remoteFile = params.get("remotefile"+fileIdx);
                String localFile = params.get("localfile"+fileIdx);
                if (remoteFile == null || remoteFile.equals("")
                        || localFile == null || localFile.equals("") )
                    break;

                remoteFile = filenameHack(remoteFile);
                localFile  = filenameHack(localFile);

                try {
                    downloadFile(session, remoteFile, localFile, encoding);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }

        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            try{ session.disconnect(); } catch(Exception ex){}
        }
    }

    private static void downloadFile(Session session, 
            String remoteFile, String localFile, String encoding) throws Exception {
        // send exec command: scp -p -f "/some/file.png"
        // -p = read file timestamps
        // -f = From remote to local
        String command = String.format("scp -p -f \"%s\"", remoteFile); 
        System.console().printf("send command: %s%n", command);
        Channel channel=session.openChannel("exec");
        ((ChannelExec)channel).setCommand(command.getBytes(encoding));

        // get I/O streams for remote scp
        byte[] buf=new byte[32*1024];
        OutputStream out=channel.getOutputStream();
        InputStream in=channel.getInputStream();

        channel.connect();

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: T<mtime> 0 <atime> 0\n
        // times are in seconds, since 1970-01-01 00:00:00 UTC 
        int c=checkAck(in);
        if(c!='T')
            throw new IOException("Invalid timestamp reply from server");

        long tsModified = -1; // millis
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(tsModified < 0 && buf[idx]==' ') {
                tsModified = Long.parseLong(new String(buf, 0, idx))*1000;
            } else if(buf[idx]=='\n') {
                break;
            }
        }

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: C0644 <binary length> <filename>\n
        // length is given as a text "621873" bytes
        c=checkAck(in);
        if(c!='C')
            throw new IOException("Invalid filename reply from server");

        in.read(buf, 0, 5); // read '0644 ' bytes

        long filesize=-1;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]==' ') {
                filesize = Long.parseLong(new String(buf, 0, idx));
                break;
            }
        }

        // read remote filename
        String origFilename=null;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]=='\n') {
                origFilename=new String(buf, 0, idx, encoding); // UTF-8, ISO-8859-1
                break;
            }
        }

        System.console().printf("size=%d, modified=%d, filename=%s%n"
                , filesize, tsModified, origFilename);

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // read binary data, write to local file
        FileOutputStream fos = null;
        try {
            File file = new File(localFile);
            fos = new FileOutputStream(file);
            while(filesize > 0) {
                int read = Math.min(buf.length, (int)filesize);
                read=in.read(buf, 0, read);
                if(read < 0)
                    throw new IOException("Reading data failed");

                fos.write(buf, 0, read);
                filesize -= read;
            }
            fos.close(); // we must close file before updating timestamp
            fos = null;
            if (tsModified > 0)
                file.setLastModified(tsModified);               
        } finally {
            try{ if (fos!=null) fos.close(); } catch(Exception ex){}
        }

        if(checkAck(in) != 0)
            return;

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'
        System.out.println("Binary data read");     
    }

    private static int checkAck(InputStream in) throws IOException {
        // b may be 0 for success
        //          1 for error,
        //          2 for fatal error,
        //          -1
        int b=in.read();
        if(b==0) return b;
        else if(b==-1) return b;
        if(b==1 || b==2) {
            StringBuilder sb=new StringBuilder();
            int c;
            do {
                c=in.read();
                sb.append((char)c);
            } while(c!='\n');
            throw new IOException(sb.toString());
        }
        return b;
    }


    /**
     * Parse key=value pairs to hashmap.
     * @param args
     * @return
     */
    private static Map<String,String> parseParams(String[] args) throws Exception {
        Map<String,String> params = new HashMap<String,String>();
        for(String keyval : args) {
            int idx = keyval.indexOf('=');
            params.put(
                    keyval.substring(0, idx),
                    keyval.substring(idx+1)
            );
        }
        return params;
    }

    private static String filenameHack(String filename) {
        // It's difficult reliably pass unicode input parameters 
        // from Java dos command line.
        // This dirty hack is my very own test use case. 
        if (filename.contains("${filename1}"))
            filename = filename.replace("${filename1}", "Korilla ABC ÅÄÖ.txt");
        else if (filename.contains("${filename2}"))
            filename = filename.replace("${filename2}", "test2 ABC ÅÄÖ.txt");           
        return filename;
    }

}
Whome
  • 10,181
  • 6
  • 53
  • 65