{"id":21387191,"url":"https://github.com/sshtools/sshapi","last_synced_at":"2025-07-13T15:31:48.889Z","repository":{"id":84133130,"uuid":"127891925","full_name":"sshtools/sshapi","owner":"sshtools","description":"SSHAPI is yet another Java library for talking to SSH servers. However, instead of directly supporting SSH, it is instead a facade for a number of supported providers, including Maverick, Ganymed, J2SSH and JSch. Think of it like SLF4J, for SSH!","archived":false,"fork":false,"pushed_at":"2024-04-06T22:21:55.000Z","size":8389,"stargazers_count":5,"open_issues_count":1,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-24T06:38:53.414Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sshtools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-04-03T10:33:21.000Z","updated_at":"2024-03-17T20:02:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"aad5d900-0e68-4713-a64a-10da718e5921","html_url":"https://github.com/sshtools/sshapi","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fsshapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fsshapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fsshapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fsshapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sshtools","download_url":"https://codeload.github.com/sshtools/sshapi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225893447,"owners_count":17540916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-22T12:12:05.132Z","updated_at":"2024-11-22T12:12:05.829Z","avatar_url":"https://github.com/sshtools.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SSHAPI\n\nSSHAPI is a clean and modern API for accessing SSH servers in Java. However, it does not supply the support for the protocol itself, or any encryption code, it instead delegates this to an *SSH Provider*, which in turn uses one of many already available SSH libraries.\n\n```java\n\ttry (SshClient client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\t\ttry(SftpClient sftp = client.sftp()) {\n\t\t\tsftp.get(\"/home/myhome/stuff.txt\", new File(\"stuff.txt\"));\n\t\t}\n\t}\n```\n\nIt was originally written with two purposes in mind.\n\n * We wanted a way to compare SSH APIs for performance to highlight issues in our own products.\n * An abstraction layer for our UniTTY product to work with multiple SSH libraries.\n \nHowever, it is now a viable complete SSH api with other advantages :-\n\n * Adds a modern API over the top of legacy APIs (e.g. try-with-resource support and non-blocking I/O).\n * If a new cipher becomes fashionable, and one provider implements it first, in many cases you can simply switch to it   with no code changes. \n * If a vulnerability is discovered in one library, switch to another, again just by swapping the provider bridge in use.\n * If you have different performance or security requirements based on configuration, you can leave the choice to users\n * Different SSH libraries have different licenses, give yourself or your users choices based on these concerns.\n * You can compare yourself the performance or behaviour of one API against another.\n * Get near native SSH performance using the libssh provider (work in progress).\n * If the provider supports non-blocking usage, it will be used, otherwise SSHAPI will simulate non-blocking usage.\n \n## Requirements\n\n * Java. As of version 2.0.0, SSHAPI requires Java 11\n * Maven if building from source.\n\n## Installation\n\nInstallation of SSHAPI is no harder than any other SSH library for Java. Using your chosen build tool (below we use Maven), add the appropriate *Provider Bridge* library to your project, and all of the appropriate dependencies will be pulled in.\n\n```xml\n\t\n\t\u003cdependency\u003e\n\t\t\u003cgroupId\u003ecom.sshtools\u003c/groupId\u003e\n\t\t\u003cartifactId\u003esshapi-maverick-synergy\u003c/artifactId\u003e\n\t\t\u003cversion\u003e2.0.0-SNAPSHOT\u003c/version\u003e\n\t\t\u003cscope\u003ecompile\u003c/scope\u003e\n\t\u003c/dependency\u003e\n```\n\nProviders include :-\n\n * sshapi-maverick-synergy (Modern, open source JAdaptive [Maverick Synergy](https://jadaptive.com/en/products/open-source-java-ssh) API)\n * sshapi-maverick-synergy-hotfixes ([Commercially supported](https://jadaptive.com/en/products/maverick-synergy/pricing) version of [Maverick Synergy](https://jadaptive.com/en/products/open-source-java-ssh))\n * sshapi-maverick16  (Commercial, Legacy JAdaptive [Maverick](https://jadaptive.com/en/products/java-ssh-client) API)\n * sshapi-sshj (Currently well maintained open source [SSHJ](https://github.com/hierynomus/sshj) API)\n * sshapi-jsch (Now uses an [updated fork](https://github.com/mwiede/jsch) of this well established API)\n * sshapi-trilead (A fork of Ganymed, itself now apparently unmaintained)\n * sshapi-ganymed (Now apparently unmaintained)\n * sshapi-libssh (experimental)\n * sshapi-openssh (incomplete experimental)\n\nNOTE, if you are using SNAPSHOT versions of the library, you will need to add the Sonatype OSS snapshot repository too.\n\n```xml\n\t\u003crepository\u003e\n\t\t\u003cid\u003eoss-snapshots\u003c/id\u003e\n\t\t\u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots\u003c/url\u003e\n\t\t\u003creleases\u003e\n\t\t\t\u003cenabled\u003efalse\u003c/enabled\u003e\n\t\t\u003c/releases\u003e\n\t\t\u003csnapshots /\u003e\n\t\u003c/repository\u003e\n```\n\n## Usage\n\nFor full usage, see the examples.\n\n### Connecting, Authenticating and Creating A Shell\n\n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var shell = client.shell()) {\n\t\tvar in = shell.getInputStream();\n\t\tvar out = shell.getOutputStream();\n\t\t\n\t\t// Do something with I/O streams\n\t}\n}\n\n```\n\n### Connecting, Authenticating and Creating A Shell with a Pseudo Tty\n\n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var shell = client.shell(\"xterm\", 80, 24, 0, 0, null)) {\n\t\tvar in = shell.getInputStream();\n\t\tvar out = shell.getOutputStream();\n\t\t\n\t\t// Do something with I/O streams\n\t}\n}\n\n```\n\n### Getting A Remote File using SFTP\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var sftp = client.sftp()) {\n\t\tsftp.get(\"/home/myhome/stuff.txt\", new File(\"stuff.txt\"));\n\t}\n}\n```\n\n### Uploading A File using SCP\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var scp = client.scp()) {\n\t\tscp.put(\"remote-name\", null, new File(\"stuff.txt\"), false);\n\t}\n}\n```\n\n### Listing A Directory\n\nThere are a few ways of doing this.\n\n#### As A Complete Array\n\nAll files will be loaded into memory at once. \n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var sftp = client.sftp()) {\n\t\tfor(var file : sftp.ls(\"/home/myhome/stuff.txt\")) {\n\t\t\tSystem.out.println(file.getName());\n\t\t}\n\t}\n}\n```\n\n#### As A Stream\n\nFor iterating over large directories.\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var sftp = client.sftp()) {\n\t\ttry(var stream = client.directory(\"/home/myhome\")) {\n\t\t\tfor(var file : stream) {\n\t\t\t\tSystem.out.println(file.getName());\n\t\t\t}\t\t\t\t\n\t\t}\n\t}\n}\n```\n\n#### Recursively\n\nUse a `FileVisitor` to recursively process an entire tree of files.\n\n```\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var sftp = client.sftp()) {\n\t\tsftp.visit(\"/home/myhome\", new SftpFileVisitor() {\n\t\t\t@Override\n\t\t\tpublic FileVisitResult visitFile(SftpFile file, BasicFileAttributes attrs) throws IOException {\n\t\t\t\tSystem.out.println(file.getName());\n\t\t\t\treturn FileVisitResult.CONTINUE;\n\t\t\t}\n\t\t});\n\t}\n}\n```\n\n### Random Access Read And Write\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var sftp = client.sftp()) {\n\t\n\t\t// Write test file\n\t\ttry (var handle = sftp.file(\"test-file.txt\", Mode.SFTP_WRITE, Mode.SFTP_CREAT)) {\n\t\t\tByteBuffer buf = ByteBuffer.allocate(16);\n\t\t\tbuf.putInt(1);\n\t\t\tbuf.putInt(2);\n\t\t\tbuf.putInt(3);\n\t\t\tbuf.putInt(4);\n\t\t\tbuf.flip();\n\t\t\thandle.writeTo(buf);\n\t\t}\n\t\t\t\n\t\t// Read 4 bytes from test file from 4th byte, which is the 2nd 'int' written above\n\t\ttry (var handle = sftp.file(\"test-file.txt\", Mode.SFTP_READ)) {\n\t\t\tByteBuffer buf = ByteBuffer.allocate(4);\n\t\t\thandle.position(4).readFrom(buf);\n\t\t\tif(buf.getInt(0) != 2)\n\t\t\t\tthrow new IllegalStateException(\"Expected to receive value of 2.\");\n\t\t}\n\t}\n}\n```\n\n### Run A Remote Command And Get The Output\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var command = client.command(\"ls /etc\")) {\n\t\tUtil.joinStreams(command.getOutputStream(), System.out);\n\t\tSystem.out.println(\"Exited with code: \" + command.exitCode());\n\t}\n}\n```\n\n### Start A Local Port Forward, Giving You Access To Remote TCP Service (e.g. web server)\n \n```java\ntry (var client = Ssh.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\ttry(var command = client.localForward(\"0.0.0.0\", 8443, \"someremoteaddress\", 443)) {\n\t\t// Simply sleep to keep the tunnel open. \n\t\tThread.sleep(600000);\n\t}\n}\n```\n\n### Authenticating Using a Public/Private Keys\n \n```java\n// Prompt for location of private key\nvar pemFile = new File(Util.prompt(\"Private key file\",\n\t\tSystem.getProperty(\"user.home\") + File.separator + \".ssh\" + File.separator + \"id_rsa\"));\n\t\t\t\ntry (var client = Ssh.open(\"me@localhost\", \n\t\tnew DefaultPublicKeyAuthenticator(new ConsolePasswordAuthenticator(), pemFile))) {\n\t// Do SSH stuff\n}\n```\n\n### Custom Configuration\n\n```java\nvar config = new SshConfiguration();\nconfig.addRequiredCapability(Capability.SFTP);\nconfig.setPreferredServerToClientMAC(\"hmac-sha1\");\nconfig.setHostKeyValidator(new ConsoleHostKeyValidator());\nconfig.setBannerHandler(new ConsoleBannerHandler());\n\n// Create the client using that configuration and connect and authenticate\ntry (var client = config.open(\"me@localhost\", new ConsolePasswordAuthenticator()) {\n\t// Do SSH stuff\n}\n```\n\n### Multiple Providers\n\n```java\n// List all of the providers and allow the user to select one\nvar providers = DefaultProviderFactory.getAllProviders();\nSystem.out.println(\"Providers :-\");\nfor (int i = 0; i \u003c providers.length; i++) {\n\tSystem.out.println(\"  \" + (i + 1) + \": \" + providers[i].getClass().getName());\n}\nvar provider = providers[Integer.parseInt(Util.prompt(\"\\nEnter the number for the provider you wish to use (1-\"\n\t+ providers.length + \")\")) - 1];\n\t\n// Create a client using that provider\ntry (var client = provider.open(new SshConfiguration(), \"me@localhost\", new ConsolePasswordAuthenticator()) {\n\t// Do SSH stuff\n}\n```\n\n### Changing A Private Key Passphrase\n\n```java\n\n// Need a provider that does IDENTITY_MANAGEMENT\nvar config = new SshConfiguration();\nconfig.addRequiredCapability(Capability.IDENTITY_MANAGEMENT);\n\n// Create the provider, then identity manager using that configuration\nvar provider = DefaultProviderFactory.getInstance().getProvider(config);\nSystem.out.println(\"Got provider \" + provider.getClass());\nvar mgr = provider.createIdentityManager(config);\n\n// Read and (optionally) decrypt key\t\nSshPrivateKeyFile pk;\nvar keyFile = new File(\"/home/myhome/.ssh/id_rsa\");\ntry(var in = new FileInputStream(keyFile)) {\n\tpk = mgr.createPrivateKeyFromStream(in);\t\t\t\n\tif (pk.isEncrypted()) {\n\t\tvar pw = Util.prompt(\"Old passphrase\");\n\t\tpk.decrypt(pw.toCharArray());\n\t}\n}\n\t\n// Change passphrase\npk.changePassphrase(newpw.toCharArray());\n\n// Write the key back out\ntry(var fout = new FileOutputStream(keyFile)) {\n\tfout.write(pk.getFormattedKey());\n}\n\t\n```\n\n### Create A Socket Factory That Is Tunneled To a Remote Host\n\n```java\nvar config = new SshConfiguration();\nconfig.addRequiredCapability(Capability.TUNNELED_SOCKET_FACTORY);\n\n// Connect, authenticate\ntry (var client = config.open(\"me@localhost\", new ConsolePasswordAuthenticator())) {\n\n\tvar sf = client.createTunneledSocketFactory();\n\n\t/*\n\t * Make a connection back to the SSH server we are connecting from and read the\n\t * first line of output. This could be any host that is accessible from the\n\t * remote SSH server, localhost:22 is just used as we know something will be\n\t * running there!\n\t */\n\ttry(var socket = sf.createSocket(\"localhost\", 22)) {\n\t\tvar reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));\n\t\tSystem.out.println(\"SSH ident: \" + reader.readLine());\n\t}\n}\n```\n\n### Authenticating Using A Local SSH Agent\n\n```java\nvar config = new SshConfiguration();\nconfig.addRequiredCapability(Capability.AGENT);\n\ntry (var client = config.open(\"me@localhost\", new DefaultAgentAuthenticator())) {\n\t// Do SSH stuff\n}\n```\n\n### Non-blocking usage\n\nSSHAPI can use a non-blocking pattern if you prefer that. For most methods, there will be an equivalent **Later** method. For example `shell()` has a `shellLater()`. These methods will not block, nor will they throw an exception. Instead, a `Future` is returned, that may be used to retrieve the shell object when it is ready, or cancel the operation. If an exception occurs during the operation, the future will also return that.\n\n#### Non-blocking Shell\n\n```java\nvar future = Ssh.openLater(\"me@localhost\", new ConsolePasswordAuthenticator())\n\n// At some point after this you can attempt to retrieve the SshClient instance from the future. This will block until it's available. You can request a timeout too\nvar client = future.get(10, TimeUnit.SECONDS);\n\n// Shells act in the same way\nvar shellFuture = client.shellLater();\nvar shell = shellFuture.get(); \n\n// To handle data coming from the remote server, set the input handler\nshell.setInput((buffer) -\u003e {\n\t// Do something with ByteBuffer (buffer is pre-flipped to limit() will be length of data, position() will be zero\n});\n\n// To send data to the shell simple use writeLater(). You use the future to \n// wait for when this actually happens.\nvar writeFuture = shell.writeLater(ByteBuffer.wrap(\"Test!\".getBytes()));\nwriteFuture.get();\n\n// Now you can close, and of course, wait for the close to complete if you want\nvar closeShellFuture = shell.closeLater();\ncloseShellFuture.get();\n\n// And finally close the client too\nvar closeFuture = client.closeLater();\ncloseFuture.get();\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshtools%2Fsshapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsshtools%2Fsshapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshtools%2Fsshapi/lists"}