使用 pysftp 验证主机密钥
Posted
技术标签:
【中文标题】使用 pysftp 验证主机密钥【英文标题】:Verify host key with pysftp 【发布时间】:2021-11-26 16:38:36 【问题描述】:我正在使用 pysftp 编写一个程序,它想根据C:\Users\JohnCalvin\.ssh\known_hosts
验证 SSH 主机密钥。
终端程序使用 PuTTY 将其保存到注册表[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
。
如何调和 pysftp 和 PuTTY 之间的区别?
我的代码是:
import pysftp as sftp
def push_file_to_server():
s = sftp.Connection(host='138.99.99.129', username='root', password='*********')
local_path = "testme.txt"
remote_path = "/home/testme.txt"
s.put(local_path, remote_path)
s.close()
push_file_to_server()
我收到的错误响应是:
E:\Program Files (x86)\Anaconda3\lib\site-packages\pysftp__init__.py:61:用户警告: 无法从 C:\Users\JohnCalvin.ssh\known_hosts 加载 HostKey。 您将需要显式加载 HostKeys (cnopts.hostkeys.load(filename)) 或 disableHostKey 检查 (cnopts.hostkeys = 无)。 warnings.warn(wmsg, UserWarning) 回溯 (最近一次通话最后):文件 “E:\OneDrive\Python\GIT\DigitalCloud\pysftp_tutorial.py”,第 14 行,在 push_file_to_server() 文件“E:\OneDrive\Python\GIT\DigitalCloud\pysftp_tutorial.py”,第 7 行,在 push_file_to_server s = sftp.Connection(host='138.99.99.129', username='root', password='********') File "E:\Program Files (x86)\Anaconda3\lib\site-packages\pysftp__init__.py",第 132 行,在 初始化 self._tconnect['hostkey'] = self._cnopts.get_hostkey(host) File "E:\Program Files (x86)\Anaconda3\lib\site-packages\pysftp__init__.py",第 71 行,在 获取主机密钥 raise SSHException("No hostkey for host %s found." % host) paramiko.ssh_exception.SSHException: No hostkey for host 138.99.99.129 成立。异常被忽略:> Traceback(大多数 最近通话最后):文件“E:\ Program Files (x86)\Anaconda3\lib\site-packages\pysftp__init__.py",第 1013 行,在 德尔 self.close() 文件“E:\Program Files (x86)\Anaconda3\lib\site-packages\pysftp__init__.py”,第 784 行,在 关闭 if self._sftp_live: AttributeError: 'Connection' object has no attribute '_sftp_live'
【问题讨论】:
你可以在pysftp
documentation找到你的问题的答案,它明确提到了这个问题here。
【参考方案1】:
一种选择是禁用主机密钥要求:
import pysftp
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
with pysftp.Connection(host, username, password, cnopts=cnopts) as sftp:
sftp.put(local_path, remote_path)
您可以在此处找到更多相关信息: https://***.com/a/38355117/1060738
重要提示:
通过设置cnopts.hostkeys=None
,您将失去对中间人攻击的保护。使用@martin-prikryl 答案来避免这种情况。
【讨论】:
是的,这就是我最终解决问题的方式。但是这样做只会忽略主机密钥。我希望能够通过读取实际密钥来保持安全性(如果可能)。 简短说明。如果路径中有一个空的 known_hosts 文件,CnOpts() 将失败。 见my answer for a solution。 我已将 hostkeys 设置为 None 但这在 docker 容器中不起作用,会发出警告并且不会建立连接。【参考方案2】:您好,如果我理解您的话,我们可能遇到了同样的问题。因此,请检查您使用的 pysftp 版本。如果是最新的 0.2.9 降级到 0.2.8。 看一下这个。 https://github.com/Yenthe666/auto_backup/issues/47
【讨论】:
不要建议人们绕过安全功能,而不解释后果!您正在失去对MITM attacks 的保护。【参考方案3】:pysftp 有一些关于主机密钥处理的错误,如下所述。 pysftp 项目似乎也被放弃了。考虑直接使用 Paramiko。 pysftp 只是 Paramiko 之上的一个包装器,它并没有添加任何真正重要的东西。见pysftp vs. Paramiko。
有关 Paramiko 中主机密钥的处理,请参阅:Paramiko "Unknown Server"
如果你想继续使用 pysftp,除非你不关心安全性,否则不要设置 cnopts.hostkeys = None
(如第二个最受支持的答案所示)。这样做会失去对Man-in-the-middle attacks 的保护。
使用CnOpts.hostkeys
(返回HostKeys
)管理受信任的主机密钥。
cnopts = pysftp.CnOpts(knownhosts='known_hosts')
with pysftp.Connection(host, username, password, cnopts=cnopts) as sftp:
known_hosts
包含服务器公钥],格式如下:
example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...
如果不想使用外部文件,也可以使用
from base64 import decodebytes
# ...
keydata = b"""AAAAB3NzaC1yc2EAAAADAQAB..."""
key = paramiko.RSAKey(data=decodebytes(keydata))
cnopts = pysftp.CnOpts()
cnopts.hostkeys.add('example.com', 'ssh-rsa', key)
with pysftp.Connection(host, username, password, cnopts=cnopts) as sftp:
虽然从 pysftp 0.2.9 开始,这种方法会发出警告,这似乎是一个错误:"Failed to load HostKeys" warning while connecting to SFTP server with pysftp
以所需格式检索主机密钥的一种简单方法是使用 OpenSSH ssh-keyscan
:
$ ssh-keyscan example.com
# example.com SSH-2.0-OpenSSH_5.3
example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...
(由于 pysftp 中的错误,此 does not work, if the server uses non-standard port - 条目以 [example.com]:port
开头 + 谨防 redirecting ssh-keyscan
to a file in PowerShell)
您也可以让应用程序自动执行相同的操作:Use Paramiko AutoAddPolicy with pysftp
(它会自动将新主机的主机密钥添加到known_hosts
,但对于已知的主机密钥,它不会接受更改的密钥)
尽管为了绝对安全,您不应该远程检索主机密钥,因为您无法确定是否已经受到攻击。
看我的文章Where do I get SSH host key fingerprint to authorize the server? 它适用于我的 WinSCP SFTP 客户端,但那里的大多数信息通常都是有效的。
如果您需要仅使用其指纹验证主机密钥,请参阅Python - pysftp / paramiko - Verify host key using its fingerprint。
【讨论】:
当cnopts.hostkeys.add()
中的hostname
参数出现在known_hosts
中时,如|1|xyzxyzxyz=|abcabcabc=
,我应该使用什么?
@marengaz 使用您在pysftp.Connection
中使用的主机名。【参考方案4】:
首先使用使用 known_hosts 文件的 Windows ssh 客户端连接到服务器。 PuTTy 将数据存储在 Windows 注册表中,但 OpenSSH 使用 known_hosts 文件,并会在您连接后在其中添加条目。 文件的默认位置是 %USERPROFILE%.ssh。我希望这会有所帮助
【讨论】:
不公平的评论。你读过这个问题吗?如何调和 pysftp 和 PuTTY 的区别?【参考方案5】:尝试使用0.2.8版本的pysftp库。
$ pip uninstall pysftp && pip install pysftp==0.2.8
然后试试这个:
try:
ftp = pysftp.Connection(host, username=user, password=password)
except:
print("Couldn't connect to ftp")
return False
为什么会这样? 基本上是 0.2.9 的 pysftp 的 bug 这里所有细节 https://github.com/Yenthe666/auto_backup/issues/47
【讨论】:
您能详细说明一下吗?我浏览了链接,实际上并没有找到任何体面的解释为什么降级会有所帮助。虽然您的声明“这里有所有详细信息” 表明应该有一些。 太棒了!!我已经安装了 0.2.9。这篇文章拯救了我的一天!【参考方案6】:我已经在我的pysftp github fork 中实现了auto_add_key
。
auto_add_key
将把密钥添加到known_hosts
如果auto_add_key=True
一旦known_hosts
中的主机存在密钥,就会检查该密钥。
有关安全问题,请参考Martin Prikryl -> answer。
尽管为了绝对安全,您不应该远程检索主机密钥,因为您无法确定是否已经受到攻击。
import pysftp as sftp
def push_file_to_server():
s = sftp.Connection(host='138.99.99.129', username='root', password='pass', auto_add_key=True)
local_path = "testme.txt"
remote_path = "/home/testme.txt"
s.put(local_path, remote_path)
s.close()
push_file_to_server()
注意:Why using context manager
import pysftp
with pysftp.Connection(host, username="whatever", password="whatever", auto_add_key=True) as sftp:
#do your stuff here
#connection closed
【讨论】:
【参考方案7】:FWIR,如果身份验证仅是用户名和密码,则将远程服务器 ip 地址添加到 known_hosts,如 ssh-keyscan -H 192.168.1.162 >> ~/.ssh/known_hosts
for ref https://www.techrepublic.com/article/how-to-easily-add-an-ssh-fingerprint-to-your-knownhosts-file-in-linux/
【讨论】:
这可能更适合作为评论并注意答案仅供参考【参考方案8】:如果您尝试通过 pysftp 连接到“普通”FTP,您必须将 hostkey 设置为 None。
import pysftp
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
with pysftp.Connection(host='****',username='****',password='***',port=22,cnopts=cnopts) as sftp:
print('DO SOMETHING')
【讨论】:
【参考方案9】:烹饪书以使用不同方式的 pysftp.CnOpts() 和 hostkeys 选项。
来源:https://pysftp.readthedocs.io/en/release_0.2.9/cookbook.html
默认情况下启用主机密钥检查。默认情况下,它将使用 ~/.ssh/known_hosts。如果您希望禁用主机密钥检查(不建议),您需要修改默认 CnOpts 并将 .hostkeys 设置为 None。
import pysftp
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
with pysftp.Connection('host', username='me', password='pass', cnopts=cnopts):
# do stuff here
要使用完全不同的 known_hosts 文件,您可以通过在实例化时指定文件来覆盖 CnOpts 以查找 ~/.ssh/known_hosts。
import pysftp
cnopts = pysftp.CnOpts(knownhosts='path/to/your/knownhostsfile')
with pysftp.Connection('host', username='me', password='pass', cnopts=cnopts):
# do stuff here
如果您希望使用 ~/.ssh/known_hosts 但添加其他已知主机密钥,您可以使用 .load 方法与更新其他 known_host 格式文件合并。
import pysftp
cnopts = pysftp.CnOpts()
cnopts.hostkeys.load('path/to/your/extra_knownhosts')
with pysftp.Connection('host', username='me', password='pass', cnopts=cnopts):
# do stuff here
【讨论】:
以上是关于使用 pysftp 验证主机密钥的主要内容,如果未能解决你的问题,请参考以下文章
Python - pysftp / paramiko - 使用其指纹验证主机密钥
使用 pysftp 针对使用自定义端口的 known_hosts 文件验证主机密钥