Python - pysftp / paramiko - Проверить ключ хоста по его отпечатку пальца

Этот код вызывает исключение. Как я могу проверить отпечаток 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, "/")

Код основан на Проверка ключа хоста с помощью pysftp.


person TamusJRoyce    schedule 18.10.2017    source источник


Ответы (2)


В зависимости от ваших потребностей вы можете использовать любой из этих двух методов:

Если вам нужно проверить только один конкретный ключ хоста

Используйте 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 только с более новыми версиями OpenSSH, которые поддерживают несколько алгоритмов отпечатков пальцев и по умолчанию используют SHA256)

Вы получите что-то вроде:

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:

(на основе Подтвердите ключ хоста с помощью pysftp)

Если вам нужно автоматизировать проверку ключа хоста по его отпечатку пальца

Например. потому что отпечаток пальца поступает из внешней конфигурации.

Я не уверен, позволяет ли это ограниченный API pysftp. Вероятно, вам придется пропустить pysftp и напрямую использовать библиотеку Paramiko (pysftp использует Paramiko для внутренних целей).

С Paramiko вы можете грамотно реализовать MissingHostKeyPolicy интерфейс .

Начнем с того, как реализован 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__ метод, чтобы сравните только отпечаток пальца. Такой экземпляр PKey затем может быть добавлен к cnopts.hostkeys.add.

OP опубликовал реализацию этого подхода в своем ответе. Предположительно для Python 3 требуется более сложная реализация, как показано в Подключение к серверу SFTP с использованием pysftp и Python 3 только с отпечатком сервера < / а>.

person Martin Prikryl    schedule 19.10.2017

Основываясь на ответе Мартина Прикрыла, ниже мое решение.

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')
person TamusJRoyce    schedule 19.10.2017
comment
Отлично. Я связал это со своим ответом. - person Martin Prikryl; 20.10.2017
comment
stackoverflow.com/questions/47586224/ - версия Python 3 - person TamusJRoyce; 24.09.2018