JSCH通过密码和公钥连接SFTP服务器的指纹判断以及Serv-U设置公钥登录

34 篇文章 1 订阅
6 篇文章 0 订阅

(零)背景

随着安全要求越来越严格,逐渐内网的FTP服务器都被替换为了基于SSH的SFTP服务器。
旧的程序也都被替换成了Java程序用JSCH来连接FTP/SFTP并下载文件。
虽然SFTP是基于SSH,但一般都是设置成用户名+密码的方式访问的。

PS:之前遇到相关的问题:
键盘交互:《SFTP服务器认证类型导致无法登录的问题》
认证指令:《华为网元设备客户端连接我方FTP服务端Serv-U的问题》

(一)主机指纹

1.1 现象和原理

第一个小问题是主机身份(指纹)判断,如果你百度了一些说法,不严格验证主机指纹,那么连接倒是OK了。但后续无法保证不受到中间人攻击(MITM Attack),简单说就是有人通过伪装成目标主机,获得你的身份信息。

所以严格验证主机指纹还是需要的:

session.setConfig("StrictHostKeyChecking", "yes"); 

这时直接登录会报异常:

reject HostKey

1.2 解决方法

当然就是记录已知主机到.ssh目录known_hosts文件中。
格式是每一行一个主机,最好把同一台主机弄成一行,不要每个IP都一行很冗余,如下:

host1,ip1,ip2,ip3,ip4 ssh-rsa AAABBBCCCxxyyzzxxyyzz==
host2,ipv4,ipv6 ssh-rsa DDDEEEFFFxxyyzzxxyyzz==

Windows下正常用户位置:

C:\Users\用户名\.ssh\known_hosts

Windows下系统服务认的“用户目录”位置:

%SystemRoot%\System32\config\systemprofile\.ssh\known_hosts

Linux下位置:

/home/用户名/.ssh/known_hosts

那么如何得到主机的身份(指纹)信息呢?
有下面三个办法:

1.2.1手动ssh

先手动ssh连接一次这台确认过的服务器。

$shion@shionlnx ~> ssh 主机名或IP

当你同意连接时,主机的信息会被记录到用户目录的known_hosts中。
一个主机因为多个IP或名称可能会重复多条,可以自行整理成上面的单条格式。

1.2.2 执行ssh-keyscan

或者,执行这个指令可以得到目标主机的身份(指纹)信息。

$shion@shionlnx ~> ssh-keygen -f "/home/用户名/.ssh/known_hosts" -R "主机名或IP"

1.2.3 程序用公钥登录

如果程序用公钥方式登录,那么和手动SSH类似。
第一次执行时,主机的信息会被记录到用户目录的known_hosts中。
今后程序就不会再提示未知指纹了(见最后的代码)。

PS:密码方式登录不会自动记录。

1.3 指纹变化

如果发生中间人攻击,或者主机指纹确实发生了变化,无论程序还是手动SSH登录都会得到大概下面的错误,并且不会让你继续登录。
程序:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host 192.168.168.XX is
aa:bb:cc:11:22:33:44:55:66:77:88:99:00:dd:ee:ff.
Please contact your system administrator.
Add correct host key in /home/用户名/.ssh/known_hosts to get rid of this message.

手动:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:saj23d4f6k7a8sjfkdlsdfjk/3dS8juih3ys.
Please contact your system administrator.
Add correct host key in /home/用户名/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/用户名/.ssh/known_hosts:1
  remove with:
  ssh-keygen -f "/home/用户名/.ssh/known_hosts" -R "shion-haier"
RSA host key for shion-haier has changed and you have requested strict checking.
Host key verification failed.

如果确认主机没有问题,只是别的原因指纹变化了。
那么需要从known_hosts中删除对应的那条记录,重新记录一次新的主机身份(指纹)。

(二)JSCH程序公钥登录

如果还没设好密钥登录,那么请自行先设置好,用ssh指令试试看。
可以参考我的:《从零开始学习大数据平台(Episode 1)》里面
章节(1.3)设置虚拟机环境【4】配置ssh密钥方式登录(免密码登录)

如果你已经设置好了密钥并且可以手动ssh免密码登录,程序用JSCH就可以这样:

jsch.addIdentity(thePrivateKeyFileName); //直接用私钥文件名
...
session = jsch.getSession(username, host, port);
session.setConfig("PreferredAuthentications", "publickey"); //指定验证方式。

函数addIdentity大概这样的:

//com.jcraft.jsch.JSch 
//Maven: com.jcraft:jsch:0.1.55 (jsch-0.1.55.jar)

public void addIdentity(String prvkey) throws JSchException
//Sets the private key, which will be referred in the public key authentication.

Parameters:
//prvkey - filename of the private key.

Throws:
//JSchException - if prvkey is invalid.

2.1 注意密钥格式

设置ssh密钥登录(免密码登录)时。
生成的密钥由于工具或版本的原因,JSCH可能用不了。
需要这样:

$shion@shionlnx ~> ssh-keygen -m PEM -t rsa

指定rsa加密方式(目前还是默认),并用了参数 -m PEM指定格式:

 -m key_format
 Specify a key format for key generation, the -i (import), -e (export) conversion options, 
 and the -p change passphrase operation.  
 The latter may be used to convert between OpenSSH private key and PEM private key formats. 
 The supported key formats are: “RFC4716” (RFC 4716/SSH2 public or private key), “PKCS8” (PKCS8 public or private key) or “PEM” (PEM public key).  
 By default OpenSSH will write newly-generated private keys in its own format, 
 but when converting public keys for export the default format is “RFC4716”.  
 Setting a format of “PEM” when generating or updating a supported private key type will cause the key to be stored in the legacy PEM private key format.

(三)Serv-U允许公钥方式

这个弄了很久一直搞不懂。
最后发现只是一个选项的问题……

3.1 设置用户公钥文件

新建或选中一个用户 >> User Information >> SSH Keys >> ManageKeys
在这里插入图片描述
Add Key,增加一个公钥文件。
这个公钥不需要是当前用户名生成的(呃……这也……)。

当然也可以用它Create Key来新建密钥。
但是这样违背了SSH的安全原则,也就是说Serv-U是服务方,它应该只保存用户的公钥(从客户机拷贝过来)而不是生成私钥拷贝到客户机(私钥不应在计算机间传输)。
在这里插入图片描述

3.2 配置公钥登录

1)在你需要登录的用户的【Limits & Settings】下。或者全局的【Limits & Settings】的 Limits页。

2)选中【Limit Type】下拉框,切换到【Password】。

3)定位到【SSH authentication type】项目双击它。PS:由于默认项目的不能编辑,所以双击不是修改而是新建一条同命项目【SSH authentication type】。

4)将 Password and Public Key 改为 Password or Public Key 。⭐️

虽然让人不太理解……
我改的是全局,大概截图如下:
在这里插入图片描述
如果不改这一项,那么你将得到返回是:

Auth fail

因为觉得不理解不爽,所以又看了一下Serv-U的日志。
发现没有任何帮助……

设置 Password and Public Key的失败记录:

[02] Mon 13Dec21 17:38:43 - (000003) Connected to 192.168.50.88 (local address 192.168.50.88, port 22)
[30] Mon 13Dec21 17:38:43 - (000003) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: none
[31] Mon 13Dec21 17:38:43 - (000003) SSH2_MSG_USERAUTH_FAILURE: login failed
[30] Mon 13Dec21 17:38:43 - (000003) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: publickey
[30] Mon 13Dec21 17:38:43 - (000003) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: publickey
[31] Mon 13Dec21 17:38:43 - (000003) SSH_MSG_DISCONNECT: client has requested a disconnect.  Reason code: 3
[02] Mon 13Dec21 17:38:43 - (000003) Closed session

设置 Password or Public Key 的成功记录:

[02] Mon 13Dec21 17:42:12 - (000004) Connected to 192.168.50.88 (local address 192.168.50.88, port 22)
[30] Mon 13Dec21 17:42:13 - (000004) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: none
[31] Mon 13Dec21 17:42:13 - (000004) SSH2_MSG_USERAUTH_FAILURE: login failed
[30] Mon 13Dec21 17:42:13 - (000004) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: publickey
[30] Mon 13Dec21 17:42:13 - (000004) SSH2_MSG_USERAUTH_REQUEST: user: Shion; service: ssh-connection; type: publickey
[02] Mon 13Dec21 17:42:13 - (000004) User "Shion" logged in
[31] Mon 13Dec21 17:42:13 - (000004) SSH2_MSG_USERAUTH_SUCCESS: successful login

(四)Java登录代码

考虑到实际场景,先尝试严格方式,如果成功则日志记录指纹已知。
但是如果没有设置known_hosts则再尝试不严格的方式,并告警指纹未知有风险。

如果密码是一个私钥文件名并且文件存在,则采用密钥方式登录,否则密码方式登录。

UserInfo 部分请继续参考键盘交互:《SFTP服务器认证类型导致无法登录的问题》

public void getConnect(String host, int port, String username,String password, boolean psv) throws Exception {
		fpassword=password;
		UserInfo ui = new MyUserInfo();
		try {
			JSch jsch = new JSch();
			String knownHosts = System.getProperty("user.home")+"/.ssh/known_hosts";
			if (new File(knownHosts).exists()) {
				jsch.setKnownHosts(knownHosts);
			}
			if (new File(password).exists()) { //如果密码位置输入的是密钥文件
				jsch.addIdentity(password);
			}
			session = jsch.getSession(username, host, port);
			session.setUserInfo(ui);
			session.setConfig("userauth.gssapi-with-mic", "no");
			session.setConfig("StrictHostKeyChecking", "yes"); // 验证 HostKey

			if (new File(password).exists()){ //如果密码位置输入的是密钥文件
				session.setConfig("PreferredAuthentications", "publickey"); // 公钥校验
			} else {
				session.setConfig("PreferredAuthentications", "password");
				session.setPassword(password);
			}
			session.connect(5000);
			CrossLog.MyLog(TNU.LogType.INFO,"主机指纹: ["+session.getHostKey().getFingerPrint(jsch)+"]已知");
		} catch (JSchException jse) {
			if (jse.getMessage().contains("reject HostKey")) {
				JSch jsch = new JSch();
				String knownHosts = System.getProperty("user.home")+"/.ssh/known_hosts";
				if (new File(knownHosts).exists()) {
					jsch.setKnownHosts(knownHosts);
				}
				if (new File(password).exists()) { //如果密码位置输入的是密钥文件
					jsch.addIdentity(password);
				}
				session = jsch.getSession(username, host, port);
				session.setUserInfo(ui);
				session.setConfig("userauth.gssapi-with-mic", "no");
				session.setConfig("StrictHostKeyChecking", "no"); // 不验证 HostKey

				if (new File(password).exists()){ //如果密码位置输入的是密钥文件
					session.setConfig("PreferredAuthentications", "publickey"); // 公钥校验
				} else {
					session.setConfig("PreferredAuthentications", "password");
					session.setPassword(password);
				}
				session.connect(5000);
				CrossLog.MyLog(TNU.LogType.WARNING,"主机指纹: ["+session.getHostKey().getFingerPrint(jsch)+"]未知!");
				CrossLog.MyLog(TNU.LogType.WARNING,"请手动确认主机指纹,避免中间人攻击(MITM Attack)危险!");
			}
			else {
				throw new Exception("连接服务器失败(JSCH)! "+jse.getMessage());
			}
		} catch (Exception e) {
			if (session.isConnected())
				session.disconnect();
			throw new Exception("连接服务器失败! "+e.getMessage());
		}
		channel = session.openChannel("sftp");
		try {
			channel.connect();
		} catch (Exception e) {
			if (channel.isConnected())
				channel.disconnect();
			throw new Exception("连接服务器失败(channel)! "+e.getMessage());
		}
		sftp = (ChannelSftp) channel;
	}

(五)后记

实际工作中,用到SFTP都是管理员分配给我们一个用户名和密码,所以无法密钥登录方式。
但软件功能完善一点,总归是好的🌝


the end

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Java 中使用 SFTP 秘钥登录时,可以使用 JSch 库来完成。JSch 是一个纯 Java 实现的 SSH2 客户端,可以通过它来连接远程服务器并执行相应的操作。 对于使用秘钥登录时,需要提供私钥和远程服务器公钥。远程服务器公钥一般是以 OpenSSH 格式存储的,可以使用以下方式来解析: 1. 打开公钥文件,读取其中的内容: ```java String publicKey = ""; try(BufferedReader br = new BufferedReader(new FileReader("path/to/public/key"))) { String line; while ((line = br.readLine()) != null) { publicKey += line + "\n"; } } ``` 2. 创建一个 `JSch` 对象,并使用 `JSch.addIdentity()` 方法加载私钥: ```java JSch jsch = new JSch(); jsch.addIdentity("path/to/private/key"); ``` 3. 将远程服务器公钥添加到 `JSch` 的 `known_hosts` 文件中: ```java JSch.setConfig("StrictHostKeyChecking", "no"); JSch.setKnownHosts("path/to/known_hosts"); ``` 在这里,我们将 `StrictHostKeyChecking` 设置为 `no`,表示不对主机的公钥进行验证(不建议在生产环境中使用)。也可以将 `known_hosts` 文件中的内容读取出来,和远程服务器公钥进行比较,从而实现公钥的验证。 4. 使用 `Session.connect()` 方法连接远程服务器: ```java Session session = jsch.getSession("username", "remote-host", 22); session.connect(); ``` 在这里,需要提供远程服务器的用户名和地址以及 SSH 端口号。 5. 连接成功后,可以使用 `ChannelSftp` 对象进行 SFTP 操作: ```java ChannelSftp sftpChannel = (ChannelSftp) session.openChannel("sftp"); sftpChannel.connect(); ``` 在这里,我们将 `Channel` 类型设置为 `sftp`,表示要进行 SFTP 操作。 以上就是在 Java 中使用 SFTP 秘钥登录时解析 OpenSSH 格式公钥的方法。需要注意的是,在实际开发中还需要考虑到安全性和异常情况的处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值