SSH(Secure Shell,安全外壳协议),是专为远程登录会话和其他网络服务提供安全性的应用层协议。在日常开发中,包括登录远程服务器、远程执行命令脚本、文件传输等,都使用了 SSH 协议的实现。而 SSHJ 就是 SSH 协议的一个 JAVA 语言实现,其功能齐全,对 SSH 协议的特性实现全面,可以很方便地在代码中实现 SSH 相关功能应用,值得 Java 开发者了解使用。
SSH协议
SSHJ 是 hierynomus 在 Github 上开源的 Java SSH 库,项目位于 https://github.com/hierynomus/sshj,目前版本为 v0.29.0。
SSHJ 功能齐全,支持从 known_hosts 文件读取验证公钥,支持公钥、密码和交互式的验证方式,支持命令、子系统和 Shell Channel,支持本地和远程端口转发,支持 SCP 安全拷贝协议,支持从版本 0 到 3 的完全的 SFTP 安全文件传输协议,支持广泛的加密、签名、压缩等的算法实现。
SSH协议和SSHJ库
SSHJ 使用方便,可以使用 Maven 添加到项目依赖,在 pom.xml 中添加
<dependency>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.29.0</version>
</dependency>
SSHJ 的主要依赖是 SLF4J,另外,一些加密算法可能会需要 BouncyCastle,而 zlib 压缩则需要依赖 JZlib。
SSHJ 也可以自行编译,需要 Java6 及以上环境,并安装有 Unlimited strength Java Cryptography Extensions (JCE) 加密扩展包,运行命令
./gradlew clean build
SSH协议与SSHJ库
SSHJ中的主要接口由 SSHClient 提供,其作为客户端用于连接 SSH 服务,每次连接都会生成一个会话 session,在一个 session 中进行具体操作。我们来看一个基本的使用例子:
package net.schmizz.sshj.examples;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.Session.Command;
import java.io.Console;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/** 执行远程命令 */
public class Exec {
private static final Console con = System.console();
public static void main(String... args)
throws IOException {
final SSHClient ssh = new SSHClient();
ssh.loadKnownHosts();
ssh.connect("localhost");
Session session = null;
try {
ssh.authPublickey(System.getProperty("user.name"));
session = ssh.startSession();
final Command cmd = session.exec("ping -c 1 toutiao.com");
con.writer().print(IOUtils.readFully(cmd.getInputStream()).toString());
cmd.join(5, TimeUnit.SECONDS);
con.writer().print("n** exit status: " + cmd.getExitStatus());
} finally {
try {
if (session != null) {
session.close();
}
} catch (IOException e) {
// 处理异常
}
ssh.disconnect();
}
}
}
这是使用 SSHJ 在远程服务器上执行命令的例子。首先创建一个 SSHClient,再加载 know_hosts,并连接到 localhost 所在的 SSH 服务。然后,使用本机用户名对应的公钥进行 SSH 登录验证,并启动会话。成功建立连接后,使用 session 的 exec 接口在 SSH 服务所在远程执行了一条 ping 命令,并从远程读取命令行输出,返回到本地命令行进行打印。最终,在完成任务后,关闭会话并断开连接。以此为基础可以实现远程命令的程序化实现,把需要手动登录 SSH 并执行命令的过程自动化。
SSHJ 实现了 SCP 安全拷贝协议,用于加密的文件在本地和远程之间拷贝复制。在 SSHClient 连接成功之后,使用 SCPFileTransfer 实现文件的上传和下载:
// SCP下载文件
ssh.newSCPFileTransfer().download("test_file", new FileSystemFile("/tmp/"));
// SCP上传文件
ssh.newSCPFileTransfer().upload(new FileSystemFile(src), "/tmp/");
SSHJ 还实现了 SFTP 安全文件传输协议。与 SCP 相比,SFTP 可靠性高,可断点续传,支持更加广泛的远程文件操作。SSHJ 中使用 SFTPClient 来作为 SFTP 的客户端,在 SSHClient 连接成功后,创建 SFTPClient,并使用 put 和 get 进行上传和下载:
// SFTP下载
final SFTPClient sftp = ssh.newSFTPClient();
try {
sftp.get("test_file", new FileSystemFile("/tmp"));
} finally {
sftp.close();
}
// SFTP上传
final SFTPClient sftp = ssh.newSFTPClient();
try {
sftp.put(new FileSystemFile(src), "/tmp");
} finally {
sftp.close();
}
利用 SSHJ,我们还就可以很方便地实现一个交互式 SSH 客户端,在本地命令行上实现对远程命令行的交互:
package net.schmizz.sshj.examples;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.Session.Shell;
import net.schmizz.sshj.transport.verification.ConsoleKnownHostsVerifier;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import java.io.File;
import java.io.IOException;
import net.schmizz.sshj.common.LoggerFactory;
/** 交互式SSH客户端 */
class RudimentaryPTY {
public static void main(String... args)
throws IOException {
final SSHClient ssh = new SSHClient();
final File khFile = new File(OpenSSHKnownHosts.detectSSHDir(), "known_hosts");
ssh.addHostKeyVerifier(new ConsoleKnownHostsVerifier(khFile, System.console()));
ssh.connect("localhost");
try {
ssh.authPublickey(System.getProperty("user.name"));
final Session session = ssh.startSession();
try {
session.allocateDefaultPTY();
final Shell shell = session.startShell();
new StreamCopier(shell.getInputStream(), System.out, LoggerFactory.DEFAULT)
.bufSize(shell.getLocalMaxPacketSize())
.spawn("stdout");
new StreamCopier(shell.getErrorStream(), System.err, LoggerFactory.DEFAULT)
.bufSize(shell.getLocalMaxPacketSize())
.spawn("stderr");
new StreamCopier(System.in, shell.getOutputStream(), LoggerFactory.DEFAULT)
.bufSize(shell.getRemoteMaxPacketSize())
.copy();
} finally {
session.close();
}
} finally {
ssh.disconnect();
}
}
}
在这个例子中,使用了会话的 startShell 来启动一个命令行,而不像我们的第一个例子那样创建会话。在此之后,使用 SSHJ 提供的 StreamCopier,进行远程命令行输出流的获取,以及本地输入流的上传,从而完成了一个实时交互的 SSH 命令行。
SSH协议与SSHJ库
SSHJ 作为一个使用 Java 语言实现的 SSH 库,其对于 SSH 协议的实现十分全面,包含的特性众多,在其所提供的 SSH 实现的比较中,SSHJ 对于协议和算法的实现覆盖程度很高,是实现 SSH 协议相关代码逻辑和应用的优秀选择。
SSHJ 项目代码质量高,历经数年的开发,项目一直处于活跃的开发和维护状态;项目代码量不大,代码结构设计较好,且提供了丰富的使用例子,值得有兴趣的开发者进行更进一步的学习研究和开源贡献。