现象
程序无法连接到某个SFTP服务器,但是可以连接其它SFTP服务器。
应用日志中只报“连接错误”,没有详细信息。
使用的是用户名/密码的方式,未使用密钥(对方服务器无我方身份信息)。
原因与分析
经过其它工具的测试和提取日志,发现服务器要求:
Auth Type: Keyboard-interactive
SFTP服务器的认证方式除了我们熟悉的,公钥(密钥),用户名/密码,其实还有几种方式。
- PublicKey - 公钥(密钥)
- Password - 用户名/密码
- Keyboard-interactive - 键盘交互
- RHosts
- HostBased - 基于主机
此例中SFTP服务器要求键盘交互,并发送了Password: 的提示,而程序没有响应。
解决办法
修改程序,增加SFTP对于键盘交互认证方式的支持。
代码
Java + JSCH
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
//...
private static Session session = null;
private static Channel channel = null;
private static ChannelSftp sftp = null;
private static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
@Override
public String getPassphrase() {
return null;
}
@Override
public String getPassword() {
return fpassword;
}
@Override
public boolean promptPassphrase(String arg0) {
return false;
}
@Override
public boolean promptPassword(String arg0) {
return true;
}
@Override
public boolean promptYesNo(String arg0) {
return false;
}
@Override
public void showMessage(String arg0) {
}
@Override
public String[] promptKeyboardInteractive(String arg0, String arg1,
String arg2, String[] arg3, boolean[] arg4) {
return null;
}
}
//...
UserInfo ui = new MyUserInfo();
JSch jsch = new JSch();
jsch.setKnownHosts("~/.ssh/known_hosts");
session = jsch.getSession(username, host, port);
session.setPassword(password);
session.setUserInfo(ui);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "yes"); // 验证 HostKey
session.setConfig(config);
session.connect();
//...
Delphi + SecureBlackbox
FSFTPClient : TElIdSFTPClient; //SFTP对象
//...
FSFTPClient.OnKeyValidate := SftpClientKeyValidate;
FSFTPClient.OnAuthenticationKeyboard:=SftpAuthenticationKeyboard;
//...
procedure YouClass.SftpClientKeyValidate(Sender: TObject; ServerKey: TElSSHKey; var Validate: Boolean);
begin
MyWrite('服务器[' + DigestToStr(ServerKey.FingerprintMD5) + ']已接收.');
Validate := true; //判断是否允许,服务器指纹(公钥MD5)
end;
procedure YouClass.SftpAuthenticationKeyboard(Sender: TObject; Prompts: TStringList; Echo: TBits; Responses: TStringList);
var
i:Integer;
begin
for i:=0 to Prompts.Count-1 do
begin
MyWrite('键盘验证提示('+IntToStr(i+1)+')"'+Prompts[i]+'",密码已发送.');
Responses.Add(FSFTPClient.Password);
end;
end;
测试结果
可以正常登录。