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

问题描述:

此代码会引发异常。如何验证SSH指纹而不将其存储在文件中?我相信下面的代码是为公钥设计的。但是,使用SFTP服务器的客户端验证了指纹,并没有为我提供公钥。Python - pysftp/paramiko - 使用其指纹验证主机密钥

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

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

如果你需要确认只有一个特定的主机密钥

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

(使用-E md5仅适用于支持多个指纹算法和默认为SHA256的OpenSSH的新版本)

你会得到类似于:

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

如果指纹匹配你有一个ES,你现在可以安全地假定tmp.pub是一个合法的公共密钥,并在代码中使用它:

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

如果您需要基于其指纹自动验证主机密钥

Eg因为指纹来自外部配置。

我不确定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中发布了此方法的实现。

根据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') 
+0

尼斯。我从我的回答中链接了这个。 –