Depending on your needs you can use either of these two methods:
In case you need to verify only one specific host key
Use ssh-keyscan
(or similar) to retrieve the host public key:
ssh-keyscan example.com > tmp.pub
The tmp.pub
will look like (known_hosts
file format):
example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0hVqZOvZ7yWgie9OHdTORJVI5fJJoH1yEGamAd5G3werH0z7e9ybtq1mGUeRkJtea7bzru0ISR0EZ9HIONoGYrDmI7S+BiwpDBUKjva4mAsvzzvsy6Ogy/apkxm6Kbcml8u4wjxaOw3NKzKqeBvR3pc+nQVA+SJUZq8D2XBRd4EDUFXeLzwqwen9G7gSLGB1hJkSuRtGRfOHbLUuCKNR8RV82i3JvlSnAwb3MwN0m3WGdlJA8J+5YAg4e6JgSKrsCObZK7W1R6iuyuH1zA+dtAHyDyYVHB4FnYZPL0hgz2PSb9c+iDEiFcT/lT4/dQ+kRW6DYn66lS8peS8zCJ9CSQ==
Now, you can calculate a fingerprint of that public key with ssh-keygen
:
ssh-keygen -l -f tmp.pub -E md5
(use the -E md5
only with newer versions of OpenSSH that support multiple fingerprint algorithms and default to SHA256)
You will get something like:
2048 MD5:c4:26:18:cf:a0:15:9a:5f:f3:bf:96:d8:3b:19:ef:7b example.com (RSA)
If the fingerprint matches with the one you have, you can now safely assume that the tmp.pub
is a legitimate public key and use it in the code:
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:
(based on Verify host key with pysftp)
In case you need to automate verification of a host key based on its fingerprint
E.g. because the fingerprint comes from an external configuration.
I'm not sure if a limited API of pysftp allows that. You probably would have to skip pysftp and use Paramiko library directly (pysftp uses Paramiko internally).
With Paramiko, you can cleverly implement MissingHostKeyPolicy
interface.
Start with how AutoAddPolicy
is implemented:
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())))
Note that in the code you have the fingerprint available in hexlify(key.get_fingerprint())
. Just compare that value against the fingerprint you have. If it matches, just return. Otherwise raise an exception,
like the RejectPolicy
does.
Another solution (which would work even with pysftp) is to implement PKey
in a way that it holds only the fingerprint. And implement its __eq__
method (or __cmp__
before Paramiko 3.0) to compare the fingerprint only. Such an instance of PKey
can then be added to cnopts.hostkeys.add
.
OP posted an implementation of this approach in his answer. Allegedly for Python 3, more complex implementation is needed, as seen in Connecting to an SFTP server using pysftp and Python 3 with just the server fingerprint.