【问题标题】:Python - pysftp / paramiko - Verify host key using its fingerprintPython - pysftp / paramiko - 使用其指纹验证主机密钥
【发布时间】:2018-03-30 14:26:45
【问题描述】:

此代码引发异常。如何验证 SSH 指纹而不将其存储在文件中?我相信下面的代码是为公钥设计的。但是使用 SFTP 服务器的客户端验证了指纹并且没有给我公钥。

import os
import shutil

import pysftp
import paramiko

connection_info = {
    'server': "example.com",
    'user': "user",
    'passwd': "password",
    'target_dir': "out/prod",
    'hostkey': "ssh-rsa 2048 d8:4e:f1:f1:f1:f1:f1:f1:21:31:41:14:13:12:11:aa",
}

def move_files_from_server_to_local(server, localpath):
    target_dir = server['target_dir']
    keydata = "d8:4e:f1:f1:f1:f1:f1:f1:21:31:41:14:13:12:11:aa"
    key = paramiko.RSAKey(data=decodebytes(keydata))
    options = pysftp.CnOpts()
    options.hostkeys.add('example.com', 'ssh-rsa', key)
    with pysftp.Connection(
                    server['server'],
                    username=server['user'],
                    password=server['passwd'],
                    cnopts=options) as conn:
        conn.get_d(target_dir, localpath)
        delete_files_from_dir(conn, target_dir)

move_files_from_server_to_local(connection_info, "/")

代码基于Verify host key with pysftp

【问题讨论】:

    标签: python ssh paramiko pysftp


    【解决方案1】:

    根据您的需要,您可以使用以下两种方法之一:

    如果您只需要验证一个特定的主机密钥

    使用ssh-keyscan(或类似的)检索主机公钥:

    ssh-keyscan example.com > tmp.pub
    

    tmp.pub 看起来像(known_hosts 文件格式):

    example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0hVqZOvZ7yWgie9OHdTORJVI5fJJoH1yEGamAd5G3werH0z7e9ybtq1mGUeRkJtea7bzru0ISR0EZ9HIONoGYrDmI7S+BiwpDBUKjva4mAsvzzvsy6Ogy/apkxm6Kbcml8u4wjxaOw3NKzKqeBvR3pc+nQVA+SJUZq8D2XBRd4EDUFXeLzwqwen9G7gSLGB1hJkSuRtGRfOHbLUuCKNR8RV82i3JvlSnAwb3MwN0m3WGdlJA8J+5YAg4e6JgSKrsCObZK7W1R6iuyuH1zA+dtAHyDyYVHB4FnYZPL0hgz2PSb9c+iDEiFcT/lT4/dQ+kRW6DYn66lS8peS8zCJ9CSQ==
    

    现在,您可以使用ssh-keygen 计算该公钥的指纹:

    ssh-keygen -l -f tmp.pub -E md5
    

    (仅在支持多种指纹算法且默认为 SHA256 的较新版本的 OpenSSH 中使用 -E md5

    你会得到类似的东西:

    2048 MD5:c4:26:18:cf:a0:15:9a:5f:f3:bf:96:d8:3b:19:ef:7b example.com (RSA)
    

    如果指纹与您的指纹匹配,您现在可以放心地假定tmp.pub 是合法的公钥并在代码中使用它:

    from base64 import decodebytes
    # ...
    
    keydata = b"""AAAAB3NzaC1yc2EAAAABIwAAAQEA0hV..."""
    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:
    

    (基于Verify host key with pysftp

    如果您需要根据指纹自动验证主机密钥

    例如因为指纹来自外部配置。

    我不确定 pysftp 的有限 API 是否允许这样做。您可能不得不跳过 pysftp 并直接使用 Paramiko library(pysftp 内部使用 Paramiko)。

    使用 Paramiko,您可以巧妙地实现MissingHostKeyPolicy interface

    AutoAddPolicy的实现方式开始:

    class AutoAddPolicy (MissingHostKeyPolicy):
        """
        Policy for automatically adding the hostname and new host key to the
        local `.HostKeys` object, and saving it.  This is used by `.SSHClient`.
        """
    
        def missing_host_key(self, client, hostname, key):
            client._host_keys.add(hostname, key.get_name(), key)
            if client._host_keys_filename is not None:
                client.save_host_keys(client._host_keys_filename)
            client._log(DEBUG, 'Adding %s host key for %s: %s' %
                        (key.get_name(), hostname, hexlify(key.get_fingerprint())))
    

    请注意,在代码中,您可以在hexlify(key.get_fingerprint()) 中找到指纹。只需将该值与您拥有的指纹进行比较即可。如果匹配,则返回。否则引发异常, 就像RejectPolicy 一样。


    另一种解决方案(即使使用 pysftp 也可以)是实现PKey,使其仅保存指纹。并实现其__cmp__ method 仅比较指纹。然后可以将PKey 的此类实例添加到cnopts.hostkeys.add

    OP 在his answer 中发布了这种方法的实现。据称对于 Python 3,需要更复杂的实现,如 Connecting to an SFTP server using pysftp and Python 3 with just the server fingerprint 所示。

    【讨论】:

      【解决方案2】:

      根据 Martin Prikryl 的回答,以下是我的解决方案。

      def trim_fingerprint(fingerprint):
          if fingerprint.startswith('ssh-rsa 2048 '):
              return fingerprint[len('ssh-rsa 2048 '):]
          return fingerprint
      def clean_fingerprint(fingerprint):
          return trim_fingerprint(fingerprint).replace(':', '')
      
      class FingerprintKey:
          def __init__(self, fingerprint):
              self.fingerprint = clean_fingerprint(fingerprint)
          def compare(self, other):
              if callable(getattr(other, "get_fingerprint", None)):
                  return other.get_fingerprint() == self.fingerprint
              elif clean_fingerprint(other) == self.get_fingerprint():
                  return True
              elif md5(other).digest().encode('hex') == self.fingerprint:
                  return True
              else:
                  return False
          def __cmp__(self, other):
              return self.compare(other)
          def __contains__(self, other):
              return self.compare(other)
          def __eq__(self, other):
              return self.compare(other)
          def __ne__(self, other):
              return not self.compare(other)
          def get_fingerprint(self):
              return self.fingerprint
          def get_name(self):
              return u'ssh-rsa'
          def asbytes(self):
               # Note: This returns itself.
               #   That way when comparisons are done to asbytes return value,
               #   this class can handle the comparison.
              return self
      

      用法:

      options = pysftp.CnOpts()
      options.hostkeys.clear()
      options.hostkeys.add('www.example.com', u'ssh-rsa', FingerprintKey("ssh-rsa 2048 d8:4e:f1:f1:f1:f1:f1:f1:21:31:41:14:13:12:11:aa"))
          with pysftp.Connection(
                          'www.example.com',
                          username='user',
                          password='password',
                          cnopts=options) as conn:
              conn.get_d('remote/filedir', 'c:/local/output')
      

      【讨论】:

      猜你喜欢
      • 2016-12-20
      • 2017-09-03
      • 2016-11-16
      • 1970-01-01
      • 1970-01-01
      • 2021-10-12
      相关资源
      最近更新 更多