181

Is there a way to programmatically obtain a SSH server key fingerprint without authenticating to it?

I'm trying ssh -v user@host false 2>&1 | grep "Server host key", but this hangs waiting for a password if key based auth is not setup.

loopbackbee
  • 4,602

8 Answers8

159

You could do this by combining ssh-keyscan and ssh-keygen:

$ file=$(mktemp)
$ ssh-keyscan host > $file 2> /dev/null
$ ssh-keygen -l -f $file
521 de:ad:be:ef:de:ad:be:ef:de:ad:be:ef:de:ad:be:ef host (ECDSA)
4096 8b:ad:f0:0d:8b:ad:f0:0d:8b:ad:f0:0d:8b:ad:f0:0d host (RSA)
$ rm $file

Edit: since OpenSSH 7.2 this oneliner works:

ssh-keyscan host | ssh-keygen -lf -

(credits to @mykhal)

Andreas Wiese
  • 10,400
  • 5
    Maybe ssh-keygen -l -f - <(ssh-keyscan host) does, though? – user Apr 28 '14 at 13:50
  • -f doesn't understand -, therefore /dev/stdin. This said, both foobar | quux and quux <(foobar) are the same, as I understand it (except <(…) not being POSIXly correct). – Andreas Wiese Apr 28 '14 at 13:57
  • @AndreasWiese they're different. foobar | quux redirect's quux's stdin. quux <(foobar) leaves quux's stdin alone but passes as its first argument the name of a pipe on the filesystem, which represents foobar's stdout. (To get a better idea of what's happening, try the same with echo instead of quux.) In this case it looks like ssh-keygen is too clever though, and it refuses to read the pipe. Not sure why. – Jack O'Connor Sep 05 '15 at 04:10
  • 60
    OpenSSH >= 7.2 ssh-keyscan is able to read from stdin: ssh-keyscan host | ssh-keygen -lf - – mykhal Mar 04 '16 at 19:14
  • 5
    Just do: ssh-keygen -l -f <(ssh-keyscan host) – Christopher Oct 28 '16 at 04:59
  • 2
    That's a rather bad expression for shell scripts, as it depends on a shell supporting it, which POSIX shell doesn't. – Andreas Wiese Oct 28 '16 at 09:22
  • 8
    ssh-keygen -l -f - does work much as expected in ssh-keygen 7.2 and above. It produces some comment lines to STDERR that can be filtered out, as mentioned in the answer by Anthony Geoghegan or ssh-keyscan host 2>/dev/null | ssh-keygen -l -f - – Cedric Knight Nov 16 '16 at 16:17
  • Note that "host" here needs to be replaced with the actual name of your host. In my case "localhost" worked. – Jack O'Connor Jun 19 '18 at 19:52
  • 15
    New versions of openssh (7.2 at least) will display the fingerprint as SHA256 (ex: 2048 SHA256:gYz11pP/v/SMzUD58jrZ+m1EFC1pvyMxvIrg4PYlvDY ) If you want it in the old format, supply -E md5 and you'll see something like 2048 MD5:0b:f5:49:d2:69:a5:49:2c:d9:45:75:87:4d:a0:7d:33. – MatrixManAtYrService Aug 21 '18 at 00:30
  • Didn't work for me got /tmp/tmp.7vgPa3mi75 is not a public key file. – FreeSoftwareServers Mar 19 '20 at 22:14
  • @FreeSoftwareServers make sure you replaced "host" part with the name of your host. E.g. ssh-keyscan 127.0.0.1 | ssh-keygen -l -f /dev/stdin – izogfif Jan 27 '21 at 14:09
138

I recently had to do this myself so I thought I’d add an answer which shows how this can be done (with versions of OpenSSH 7.2 or newer) in one line using process substitution:

ssh-keygen -lf <(ssh-keyscan localhost 2>/dev/null)

(replace localhost with the hostname here)


The following text explains how these commands work and highlights some of the differences in behaviour between older and newer versions of the OpenSSH utilities.

Fetch public host keys

The ssh-keyscan command was developed so that users can obtain public host keys without needing to authenticate to the SSH server. From its man page:

ssh-keyscan is a utility for gathering the public ssh host keys of a number of hosts. It was designed to aid in building and verifying ssh_known_hosts files.

Key type

The type of key to be fetched is specified using the -t option.

  • rsa1 (obsolete SSH Protocol version 1)
  • rsa
  • dsa
  • ecdsa (recent versions of OpenSSH)
  • ed25519 (recent versions of OpenSSH)

In modern OpenSSH releases, the default key types to be fetched are rsa (since version 5.1), ecdsa (since version 6.0), and ed25519 (since version 6.7).

With older versions of ssh-keyscan (before OpenSSH version 5.1), the default key type was the out-dated rsa1 (SSH Protocol 1) so the key types would need to be explicitly specified:

ssh-keyscan -t rsa,dsa hostname

Get fingerprint hashes of Base64 keys

ssh-keyscan prints the host key of the SSH server in Base64-encoded format. To convert this to a fingerprint hash, the ssh-keygen utility can be used with its -l option to print the fingerprint of the specified public key.

If using Bash, Zsh (or the Korn shell), process substitution can be used for a handy one-liner:

ssh-keygen -lf <(ssh-keyscan hostname 2>/dev/null)

Note: With versions of OpenSSH before 7.2, the functions used by ssh-keygen to read files, did not handle named pipes (FIFOs) very well so this method wouldn’t work, thus requiring the use of temporary files.

Hashing algorithms

Recent versions of ssh-keygen print SHA256 fingerprint hashes of the keys. To get MD5 hashes of the server key fingerprints (the old behaviour), the -E option can be used to specify the hash algorithm:

ssh-keygen -E md5 -lf <(ssh-keyscan hostname 2>/dev/null)

Using a pipeline

If using a POSIX shell (such as dash) which doesn’t feature process substitution, the other solutions using temporary files will work. However, with newer versions of OpenSSH (since 7.2), a simple pipeline can be used since ssh-keygen will accept - as a filename for the standard input stream, allowing a one-line pipeline command.

ssh-keyscan hostname 2>/dev/null | ssh-keygen -E md5 -lf -
izogfif
  • 103
  • Nice and thorough answer, this is certainly better than having a temporary file! May I suggest you provide a TL;DR in the beginning with the process substitution version, to make impatient folks find it faster? :) – loopbackbee Mar 09 '16 at 21:07
  • 5
    Does not seem to work on Ubuntu 14.04 LTS; I get an error "/dev/fd/63 is not a public key file". The subprocess does work. – Melle Oct 18 '16 at 07:26
  • @melleb I found the same thing on an 12.04 system that I have access to. I suspect that ssh-keygen from older versions of OpenSSH have a problem reading from the FIFO / named pipe. I'll look into this (and update my answer) when I get some free time. – Anthony Geoghegan Oct 18 '16 at 09:51
  • 4
    @melleb After spending my luch-time downloading various source code releases and inserting debugging printf statements in the do_fingerprint() function, I found that with versions of OpenSSH before 7.2, the functions used by ssh-keygen to read files, did not handle named pipes (FIFOs) very well so the process substitution method would not work. – Anthony Geoghegan Oct 18 '16 at 13:35
  • This works, but if using it to verify a fingerprint, users should be aware that there's a race condition: the fingerprint you are checking with this command isn't necessarily that of the key you fetch, unless you dump the key before calling ssh-keygen on it. – CodeGnome Aug 31 '18 at 17:11
  • You write 'this can be done (with versions of OpenSSH 7.2 or newer) in one line using process substitution' - but with OpenSSH >= 7.2 you can already pipe the ssh-keyscan output. Thus, perhaps you want to start with the pipe version and add the process substitution variant at the end. Or perhaps just leave it out as such old ssh versions perhaps aren't relevant anymore, in 2020. – maxschlepzig May 18 '20 at 08:22
  • Not sure about the comment of @CodeGnome, anything else hints at this answer to be the one that should be accepted. It removes the unneeded output and is a one-liner. I tested ssh-keygen -lf <(ssh-keyscan 192.168.31.xy 2>/dev/null) with a remote computer of my local network. – questionto42 Jan 23 '22 at 18:06
28

nmap provides this ability by using the ssh-hostkey script.

To return the key's hexadecimal fingerprint:

$ nmap [SERVER] --script ssh-hostkey

To return the key's content:

$ nmap [SERVER] --script ssh-hostkey --script-args ssh_hostkey=full

To return the key's visual bubble

$ nmap [SERVER] --script ssh-hostkey --script-args ssh_hostkey='visual bubble'

To return all of the above:

$ nmap [SERVER] --script ssh-hostkey --script-args ssh_hostkey=all

Source: nmap docs

Creek
  • 5,062
  • 3
    Do these examples assume that SSH is always running on port 22 ? What if ssh listens on a non-standard port ? – Martin Vegter Dec 01 '14 at 21:33
  • 3
    @MartinVegter (paraphrasing Guarin42, who couldn't comment:) nmap has the -p option which can specify a port, e.g. -p 22000. It's also possible to use the -vv option to increase the verbosity (amount of information given) – loopbackbee Jan 29 '15 at 11:52
  • 2
    tried this out today - did not get any of the script promised/related outputs, only nmap progress and general info. – Alexander Stohr May 17 '22 at 09:29
  • worked perfectly, it even found the different ports ssh was running on. Another useful arg is ssh_hostkey=sha256 to show the current openssh standard fingerprint ;) – xeruf Nov 14 '22 at 23:55
26

The simple answer when you already have access to the server is:

ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub

Now, you might not be using the RSA key: if when connecting, ssh tells you

ECDSA key fingerprint is SHA256: XXXXX

You need to use /etc/ssh/ssh_host_ecdsa_key.pub instead. (notice _ecdsa_). A more universal command that lists all keys can thus be constructed (source):

for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$f"; done

You could write down the list when setting the server up, for future reference. Another option is to store them in DNS records (Archive):

  1. Add the following DNS record: <hostname> IN SSHFP <key-type> <hash-type> <fingerprint> (ssh-keygen can print this line for you if you give it the hostname: ssh-keygen -r hostname -f /etc/ssh/ssh_host_ecdsa_key.pub)
  2. Connect with ssh -o VerifyHostKeyDNS=yes user@hostname or enable it by default by adding VerifyHostKeyDNS=yes to the client config.

From the ssh-keygen (1) manpage:

-l Show fingerprint of specified public key file. For RSA and DSA keys ssh-keygen tries to find the matching public key file and prints its fingerprint. If combined with -v, a visual ASCII art representation of the key is supplied with the fingerprint.

-f filename Specifies the filename of the key file.

-r hostname Print the SSHFP fingerprint resource record named hostname for the specified public key file.

-E fingerprint_hash Specifies the hash algorithm used when displaying key fingerprints. Valid options are: “md5” and “sha256”. The default is “sha256”.

You may want to use the last option -E md5 when connecting from older ssh clients that default to printing the md5 key. Alternatively, from these clients, let ssh print the SHA256 with ssh -o FingerprintHash=sha256 host

MayeulC
  • 435
  • 10
    This is the only answer that is of any use, the other answers just ask "what is the key of some-remote-host" which will print the key of the MITM if there is one… not a good way to verify a host key, it's like asking the person asking for your credit card "are you my banker?". – Suzanne Soy Jul 27 '21 at 23:56
  • 2
    @SuzanneSoy Yes, you need to use (and trust) some other 'channel' to verify a key (or its fingerprint). I think this and other answers implicitly assume you're doing that, otherwise, as you point out, there's no point in generating/calculating a key's fingerprint at all. – Kenny Evitt Jan 15 '22 at 21:58
  • 1
    Unlike some of the top answers, this answer actually works, and gives complete and usable info (e.g. the ECDSA key fingerprint that you actually see when doing the ssh connection)). – limist Aug 29 '22 at 23:36
  • @SuzanneSoy Technically, you need some pre-trusted channel to the server to run this command as well, otherwise you don't know where the command is actually running. That could be something other than SSH, but any of the commands that take a hostname can be reliably run in that scenario as well by querying localhost. On the other hand, if you have multiple different users trying to access the server, the remote commands can be used to check if they agree on the transmitted host key, which could be useful, without ever logging into the server. – IMSoP Aug 07 '23 at 11:21
9

filezilla displays keys hashed with md5 in hexadecimal format.

to find this on your ubuntu linux machine use this command:

ssh-keygen -l -E md5 -f <(ssh-keyscan localhost 2>/dev/null)

note: replace "localhost" with the ip of the machine you wish to check.

Cameron
  • 91
  • This worked for me on Ubuntu 18, but note, you will get difference results for localhost 127.0.0.1 or domain.tld. Check the fingerprint for the URL you are concerned with~! – FreeSoftwareServers Mar 19 '20 at 22:16
5

Here is a shell script (mainly Bourne shell but using local keyword, which is available in most modern /bin/sh) I've written to do this. Use it like ssh-hostkey hostname. It will show both the sha256 and md5 format fingerprints for all hostkeys for the given hostname or IP address. You can also manually specify "md5" or "sha256" as the second argument to only show that particular format.

It uses a temporary file instead of piping to make it compatible with older OpenSSH packages (as described in other answers). The temporary file uses /dev/shm (shared memory) if available.

#!/bin/sh
usage () {
  printf '%s\n' "Usage: ssh-hostkey HOSTNAME [FPRINTHASH]"
}

ssh_hostkey () {
  local host="$1"
  local fprinthash="$2"
  local tmp=

  case "$host" in
    -h|--help|'')
      usage >&2
      return 1
      ;;
  esac

  case "$fprinthash" in
    md5|sha256|'') true;;
    *)
      usage >&2
      printf '%s\n' "Fingerprint hash may be 'md5' or 'sha256'" >&2
      return 2
      ;;
  esac

  if test -d /dev/shm
  then tmp="$(mktemp -d -p /dev/shm)"
  else tmp="$(mktemp -d)"
  fi

  trap 'trap - INT TERM EXIT; rm -rf "$tmp"' INT TERM EXIT
  ssh-keyscan "$host" > "$tmp/f" 2> /dev/null
  case "$fprinthash" in
    sha256|'') ssh-keygen -l -f "$tmp/f" 2> /dev/null;;
  esac
  case "$fprinthash" in
    md5|'') ssh-keygen -l -E md5 -f "$tmp/f" 2> /dev/null;;
  esac

  trap - INT TERM EXIT
  rm -rf "$tmp" > /dev/null 2>&1
}

ssh_hostkey "$@"
ejm
  • 601
  • This is a nice demo of wrapping lower level tools with command line options. Includes a bonus of how to use shared memory /dev/shm which I never knew was so convenient to do. Thanks for sharing! – Core Jul 23 '20 at 19:49
4

For my own server I use this:

ssh-keygen -l -E md5 -f <(cat /etc/ssh/ssh_host_*_key.pub)
ssh-keygen -l -E sha256 -f <(cat /etc/ssh/ssh_host_*_key.pub)
ggrandes
  • 141
  • 3
0

As I received is not a public key file with the accepted answer, I used this instead:

timeout 1 ssh -q -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/tmp/sshkey.tmp <hostname>; cat /tmp/sshkey.tmp && rm /tmp/sshkey.tmp
mgutt
  • 467