如何使用 Python 将主机的 public 密钥文件 (.pub) 添加到 SFTP 连接?

How to add the host's public key file (.pub) to a SFTP connection using Python?

我写了一个简单的程序,可以成功连接到主机的 SFTP 站点并可以检索文件。

import pysftp

cnopts = pysftp.CnOpts()
cnopts.hostkeys = None   

srv = pysftp.Connection(host=FTP_SITE, username=DOWNLOAD['USERNAME'], 
                        password=DOWNLOAD['PASSWORD'], cnopts=cnopts)
data = srv.listdir()

file_get = srv.get(get_file, localpath='C:/Users/Me/Desktop/test.csv')

虽然我无法将主机的 public 密钥添加到 SFTP 连接。我有一个主机 public 密钥文件,格式如下:

---- BEGIN SSH2 PUBLIC KEY ----
Comment: "Created by Ipswitch WS_FTP Pro"
AAAAB3NzaC1kc3MAAACBAMtINmL43m2CttyjDfHDWNJT9C+ik1OTgJSB5WRpw0G
i0HdYSrJqjUvDQhwCzbt7JRX2V9yCO9xlieUzbkDLSJ7Y8a5g5G4A1FsFEXS1kf
yZuOLBSXxs3+ibTXTMmkAyXq0FzapW6Oka6OvZW1aGfHeBbUADueJnXhyyw1+UL
1uVAAAAFQClKT+RhjhgDGbbLxl1cROQRTaoJwAAAIEAqiAV9hTPrcH6lXBb2GVz
R8TLIchLSOweOme+RK5WqN9sEPgxoo7s3xoXDzv3KJtuPAx7XRvJ/4jdcgbpQLe
1scm9LtczccG/lC583pa7WD9SUbx6mDVe8BbtHfFrp24JIkqaD47iZDYxozi8Ob
As+vny9AcfKgBbjqyTcOYVKkMAAACBALBk3DPcUCxzGXqhgi4fwyv+ze8beyGxv
2uXM1LgLjwdIRcojqZhyziDcUaRXeIlUYVFC/nC+mTItpuveZMj4xZSPyTlfxjl
E36zkbdbSGvNbvO+jP1qupp2fRDWiRSrMr0MBUzFltIocVlcXMXEl/NfEm5h6vu
BWyWQVKtaEPV0
---- END SSH2 PUBLIC KEY ----

有谁知道添加主机 public 密钥的正确方法吗?我已经检查了 pysftp https://pysftp.readthedocs.io/en/release_0.2.9/ and other tutorials http://www.pythonforbeginners.com/modules-in-python/python-secure-ftp-module 的文档,但没有运气。


编辑:我尝试了 Martin Prikryl 的建议。我首先将密钥文件格式化为格式

sftp.hostsite.com ssh-rsa AAAAB3NzaC1k...m5h6vuBWyWAAAAB3Nz

然后我尝试将代码修改为:

key_file = 'host_site_key.pub'
cnopts = pysftp.CnOpts()
cnopts.hostkeys.load(path_to_key_file + key_file)
srv = pysftp.Connection(host=SFTP_SITE, username=DOWNLOAD['USERNAME'], 
                    password=DOWNLOAD['PASSWORD'], cnopts=cnopts)

但是,我得到以下堆栈跟踪

SSHException                              Traceback (most recent call last)
<ipython-input-8-52797202ff65> in <module>()
      3 
      4 srv = pysftp.Connection(host=SFTP_SITE, username=DOWNLOAD['USERNAME'], 
----> 5                         password=DOWNLOAD['PASSWORD'], cnopts=cnopts)
      6 data = srv.listdir()

C:\Users\Alex\Anaconda2\lib\site-packages\pysftp\__init__.pyc in __init__(self, host, username, private_key, password, port, private_key_pass, ciphers, log, cnopts, default_path)
    130         # check that we have a hostkey to verify
    131         if self._cnopts.hostkeys is not None:
--> 132             self._tconnect['hostkey'] = self._cnopts.get_hostkey(host)
    133 
    134         self._sftp_live = False

C:\Users\Alex\Anaconda2\lib\site-packages\pysftp\__init__.pyc in get_hostkey(self, host)
     69         kval = self.hostkeys.lookup(host)  # None|{keytype: PKey}
     70         if kval is None:
---> 71             raise SSHException("No hostkey for host %s found." % host)
     72         # return the pkey from the dict
     73         return list(kval.values())[0]

SSHException: No hostkey for host sftp.hostsite.com found.

我也尝试了第二个建议,我将密钥硬编码为字节字符串,然后将其作为 RSAkey 加载,但仍然没有成功。

key_hardcode_rsa = b'AAAAB3NzaC1k...AAB3Nz'
key = paramiko.RSAKey(data=bytes.decode(key_hardcode_rsa))
SSHException                              Traceback (most recent call last)
<ipython-input-29-f3a4ae42127d> in <module>()
----> 1 key = paramiko.RSAKey(data=bytes.decode(key_hardcode_rsa))

C:\Users\Alex\Anaconda2\lib\site-packages\paramiko\rsakey.pyc in __init__(self, msg, data, filename, password, key, file_obj)
     54                 raise SSHException('Key object may not be empty')
     55             if msg.get_text() != 'ssh-rsa':
---> 56                 raise SSHException('Invalid key')
     57             self.key = rsa.RSAPublicNumbers(
     58                 e=msg.get_mpint(), n=msg.get_mpint()

SSHException: Invalid key

我在这里查看了paramiko的源代码https://github.com/enjrolas/lumenToy/tree/master/env/lib/python2.7/site-packages/paramiko

def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None):
    self.n = None
    self.e = None
    self.d = None
    self.p = None
    self.q = None
    if file_obj is not None:
        self._from_private_key(file_obj, password)
        return
    if filename is not None:
        self._from_private_key_file(filename, password)
        return
    if (msg is None) and (data is not None):
        msg = Message(data)
    if vals is not None:
        self.e, self.n = vals
    else:
        if msg is None:
            raise SSHException('Key object may not be empty')
        if msg.get_text() != 'ssh-rsa':
            raise SSHException('Invalid key')
        self.e = msg.get_mpint()
        self.n = msg.get_mpint()
    self.size = util.bit_length(self.n)

def get_text(self):
    """
    Fetch a string from the stream.  This could be a byte string and may
    contain unprintable characters.  (It's not unheard of for a string to
    contain another byte-stream Message.)
    @return: a string.
    @rtype: string
    """
    return u(self.get_bytes(self.get_int()))

def get_bytes(self, n):
    """
    Return the next ``n`` bytes of the message (as a `str`), without
    decomposing into an int, decoded string, etc.  Just the raw bytes are
    returned. Returns a string of ``n`` zero bytes if there weren't ``n``
    bytes remaining in the message.
    """
    b = self.packet.read(n)
    max_pad_size = 1 << 20  # Limit padding to 1 MB
    if len(b) < n < max_pad_size:
        return b + zero_byte * (n - len(b))
    return b

def get_int(self):
    """
    Fetch an int from the stream.
    @return: a 32-bit unsigned integer.
    @rtype: int
    """
    return struct.unpack('>I', self.get_bytes(4))[0]

所以源代码显示 msg 是从 data 参数创建的对象,它是传入的 RSA 密钥字节串。我如何格式化我的密钥以便 paramiko.message.get_text 函数将 return 'ssh-rsa'?

Verify host key with pysftp


除了我对上述问题的回答,你需要知道你的密钥是ssh-dssDSSKey),而不是ssh-rsaRSAKey)。

我也不明白,你从哪里弄来的...m5h6vuBWyWAAAAB3Nz。您的 .pub 文件中没有这样的序列。

您的条目应该是这样的:

sftp.hostsite.com ssh-dss AAAAB3NzaC1kc3MAAACBAMtINmL43m2CttyjDfHDWNJT9C+ik1OTgJSB5WRpw0Gi0HdYSrJqjUvDQhwCzbt7JRX2V9yCO9xlieUzbkDLSJ7Y8a5g5G4A1FsFEXS1kfyZuOLBSXxs3+ibTXTMmkAyXq0FzapW6Oka6OvZW1aGfHeBbUADueJnXhyyw1+UL1uVAAAAFQClKT+RhjhgDGbbLxl1cROQRTaoJwAAAIEAqiAV9hTPrcH6lXBb2GVzR8TLIchLSOweOme+RK5WqN9sEPgxoo7s3xoXDzv3KJtuPAx7XRvJ/4jdcgbpQLe1scm9LtczccG/lC583pa7WD9SUbx6mDVe8BbtHfFrp24JIkqaD47iZDYxozi8ObAs+vny9AcfKgBbjqyTcOYVKkMAAACBALBk3DPcUCxzGXqhgi4fwyv+ze8beyGxv2uXM1LgLjwdIRcojqZhyziDcUaRXeIlUYVFC/nC+mTItpuveZMj4xZSPyTlfxjlE36zkbdbSGvNbvO+jP1qupp2fRDWiRSrMr0MBUzFltIocVlcXMXEl/NfEm5h6vuBWyWQVKtaEPV0