Skip to content

Instantly share code, notes, and snippets.

@maxtaco
Last active December 14, 2015 11:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxtaco/5080023 to your computer and use it in GitHub Desktop.
Save maxtaco/5080023 to your computer and use it in GitHub Desktop.
Access hashed ssh known_hosts with paramiko.
import os.path
import paramiko
from hashlib import sha1
from hmac import HMAC
class KnownHostsRegistry (object):
"""
Read in the user's SSH known_hosts file, and perform lookups,
on either plaintext hostnames, or those that have been obscured
with known_hosts hashing (see here: http://nms.csail.mit.edu/projects/ssh/)
"""
def __init__ (self):
self.hosts = {}
self.hashes = []
def load (self):
self.__loadOne(".ssh")
self.__loadOne("ssh")
if len(self.hosts) is 0:
print("Could not load any known SSH hosts; failure ahead")
self.__findHashes()
return self
def __findHashes(self):
for k in self.hosts.keys():
parts = k.split("|")
if len(parts) is 4 and parts[1] is "1":
v = [ p.decode('base64') for p in parts[2:4]]
self.hashes.append(tuple([k] + v))
def __loadOne(self, dir):
f = os.path.expanduser(os.path.join("~", dir, "known_hosts"))
if os.path.exists(f) and os.path.isfile(f):
tmp = paramiko.util.load_host_keys(f)
self.hosts.update(tmp)
def __findHashedHostname(self,hostname):
for (key,salt,res) in self.hashes:
hmac = HMAC(salt, None, sha1)
hmac.update(hostname)
ours = hmac.digest()
if ours == res:
return self.hosts.get(key)
return None
def lookup (self, hostname):
"""Find a row for the given hostname; either in plaintext
or as hashed hostname as per Jayeon's patch to ssh, which is
standard on Linux but not on Mac."""
row = self.hosts.get(hostname)
if not row:
row = self.__findHashedHostname(hostname)
return row
def verify (self, hostname, theirs):
ok = False
err = None
typ = theirs.get_name()
row = self.lookup(hostname)
if row:
# get_name() return the type of key, like 'ssh-rsa'
# or 'ssh-dsa', etc...
ours = row.get(typ)
if not row:
err = "No keys found for hostname {h}"
elif not ours:
err = "No key of type {t} found for hostname {h}"
elif ours != theirs:
err = "Wrong {t} key found for hostname {h}: key changed!"
else:
ok = True
if err:
err = err.format(h=hostname, t=typ)
return (ok,err)
_s = None
def singleton():
global _s
if not _s: _s = KnownHostsRegistry().load()
return _s
def lookup(hostname):
return singleton().lookup(hostname)
def verify(hostname, theirs):
return singleton().verify(hostname, theirs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment