Thursday, October 5, 2017

SSH HashKnownHosts File Format

The HashKnownHosts option to the OpenSSH client causes it obfuscate the host field of the ~/.ssh/known_hosts file. Obfuscating this information makes it harder for threat actors (malware, border searches, etc...) to know which hosts you connect to via SSH.

Hashing defaults to off, but some platforms turn it on for you:

 chris:~$ grep Hash /etc/ssh/ssh_config   
   HashKnownHosts yes  

Here's an entry from my known_hosts file:

 |1|NWpzcOMkWUFWapbQ2ubC4NTpC9w=|ixkHdS+8OWezxVQvPLOHGi2Oawo= ecdsa-sha2-nistp256 AAAAE2Vj<...>ZHNLpyJsv  

There's one record per line, with the fields separated by spaces. The first field is the remote host (SSH server) identifier.

In this case, the leading characters |1| in the host identifier are the magic string (HASH_MAGIC). It tells us that the field is hashed, rather than a plaintext hostname (or address). The remaining characters in the field comprise two parts: a 160-bit salt (random string) and a 160-bit SHA1 hash result. Both values are base64 encoded.

The various OpenSSH binaries that use information in this file feed both the remote hosts name (or address) and the salt to the hashing function in order to produce the hash result:

So, lets validate a host entry against this record the hard way. The entry above is for an IP address:

 chris:~$ host=""  
 chris:~$ salt_from_file="NWpzcOMkWUFWapbQ2ubC4NTpC9w="  
 chris:~$ salt_hexdump=$(echo $salt_from_file | base64 --decode | xxd -p)  
 chris:~$ echo -n $host | openssl sha1 -binary -mac HMAC -macopt hexkey:$salt_hexdump | base64  

The resulting string (ixkHdS+8OWezxVQvPLOHGi2Oawo=) is the base64 encoded hash result produced by inputting our host IP and the salt we found in the file. It's the same string that we saw in the known_hosts entry, so we know that this entry is for the host

When adding a new record to known_hosts, the salt is a random value invented on the spot. The hash is calculated and the salt, hash and key details are written to the file.

When trying to find a record in an existing known_hosts file, the SSH program can't pick the right line directly. Instead it has to take the hostname (address) it's looking for, and compute the hash using the salt found on each line. When (if) it finds a match, then that's the line it was looking for. SHA1 happens pretty fast on modern hardware, but depending on your use case, this may be a bunch of wasted effort, particularly on systems where there's no point in obfuscating the list of SSH servers to which we connect.

These folks drew the cocktail shaker.