使用 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'

【问题讨论】:

你可以在pysftpdocumentation找到你的问题的答案,它明确提到了这个问题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 验证主机密钥的主要内容,如果未能解决你的问题,请参考以下文章

使用 pysftp 验证主机密钥

Python - pysftp / paramiko - 使用其指纹验证主机密钥

使用 pysftp 针对使用自定义端口的 known_hosts 文件验证主机密钥

错误:pysftp 中的“无法加载主机密钥”[重复]

使用 pysftp 连接到 SFTP 服务器时出现“加载主机密钥失败”警告

pysftp 忽略禁用的主机密钥检查