Matching SSHFP with SSH public key

This blog talks about:

1. Format of SSHFP resource record

2. SSH public key distribution

3. Matching SSHFP with given public key

SSHFP Resrouce Record

SSHFP RR contains three staffs:

1. Algorithm

2. Fingerprint type

3. Fingerprint

On any SSH server, you can get the SSHFP record by running ssh-keygen -r hostname.


Example:

ns1 IN SSHFP 1 1 b4265811ab6574ea4f107a62aa61a48f223d855d
ns1 IN SSHFP 1 2 9c3dd279d4380983446af3f2883e8bc6c333255b9e89cb6d35f940b167908467
ns1 IN SSHFP 2 1 b5e8049bae6476b7f8fd74e7c85b1ebf593a5d31
ns1 IN SSHFP 2 2 89a13ac510e3ac61e32b985c0ab3468b00080425424b7b5d3472b025108e7f35
ns1 IN SSHFP 3 1 1b2fd14610b5d86dffb3e9eb6aba3e8fbba10bd8
ns1 IN SSHFP 3 2 0acc7b23c229b54f65776cc02adf6c2cf412662198d48139e4c9e6f918dc8c32
ns1 IN SSHFP 4 1 ee4f18e974e62e4bd6c55e5459a0778b28178954
ns1 IN SSHFP 4 2 7f92b60a315b56685d15e326b604384f5d460ba5d6e943e62f41120f716a7741

ns1 was the hostname. IN and SSHFP are the RR options. Rest of the records are what I mentioned above and will be illustrated as follow.


Public key algorithm (value: algorithm name)

0: reserved (RFC 4255 by 2006 Jan)

1: RSA (RFC 4255 by 2006 Jan)

2: DSS (RFC 4255 by 2006 Jan)

3: ECDSA (RFC 6594 by 2012 Arp)

4: Ed25519 (RFC 7579 by 2015 Mar)


Fingerprint type (value: fingerprint type)

0: reserved (RFC 4255 by 2006 Jan)

1: SHA-1 (RFC 4255 by 2006 Jan)

2: SHA-256 (RFC 6594 by 2012 Arp)

SSH public key and its distribution

In directory on SSH server /etc/ssh, there are multiple files which store public/private key paris. We take ECDSA public key as an instance. (About key exchange negotiation you may need to visit wiki). /etc/ssh/ssh_host_ecdsa_key file is the ECDSA private key. Corresponding public key contained in /etc/ssh/ssh_host_ecdas_key.pub

Open ECDSA public key file on the same VM:

Remove the prefix 'ecdsa-sha2-nistp256' and suffix 'root@VM', the middle section is the real public key. More specifically, it's the base64 encoded public key. You can also get this part by command awk '{print $2}' ssh_host_ecdsa_key.pub.


Now let's examine how SSH public key is distributed, by server, to client.

According to this section from RFC4253:

we can see the public key (K_S) should be sent from server to client as plaintext. That is to say, we should be able to find the public key from packets.


Now let's have a look at the packets.

The key response is included in the 'key exchange reply' packet. Click on the packet, we have:

In the section of 'SSH Protocol', expand the first 'SSH Version 2' dropdown. (Others are not related, you should explore by your own if you are interested).

The highlighted part is the public key (not base64 encoded).

If you noticed all the bold words about base64 so far, you may raise assumption that if we do a base64 encoding on the highlighted section, the output should be same with the middle section of file 'ssh_host_ecdsa_key.pub' mentioned before.

Now run the command awk '{print $2}' ssh_host_ecdsa_key_pub | base64 -d to see what happens. If you want to try the example case and verify your operation with mine case, you can just run echo AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCxJdInpzYZX1b4OTo9Ap+8XNqB/ACtKiV/bmPhnHKKgCXCreEHqvHg1tDv5+Bfh4+SZb2+XSV9KDXQrCzXswt8= | base64 -d

The output should be like:

It's same with the highlighted section in the packet, right? Yes, this is how public key distributed.

Some explanation: public key is always stored in form of base64 on the server. One of the reason should be we want to present it as printable characters for display to users. (Base64 encoding replaces all the raw bytes to 64 readable characters. Details please head to wiki.) But we don't have to do this when transfering it to the other side through packets as non-expert users don't care what is on the wire. If you ask why not sending them also in form of base64, I don't know so far, that's how it works.

Then we should be able to recreate the encoded file by encoding the bytes with base64. It was a hard time to find the right input string from packet to be encoded and get exactly same output with the file, but it's provided:

0000001365636473612d736861322d6e69737470323536000000086e6973747032353600000041042c49
7489e9cd8657d5be0e4e8f40a7ef1736a07f002b4a895fdb98f8671ca2a00970ab7841eabc7835b43bf9
f817e1e3e4996f6f97495f4a0d742b0b35ecc2df
You should be able to find the correct section if you need by comparing the example.


Conclusion: SSH public key is stored as base64 coding. It's distributed from server to client by bytes, which is from decoding the file '/etc/ssh_host_ecdsa_key.pub'

Matching SSHFP and public key

We still use the same example from the previous chapters. Now we have the SSHFP resource records, base64 encoded public key and hex_string of public key read from packet. We want to establish a match. More specifically, we will explore how 'ssh-keygen -r hostname' works and how to match it with a given public key.

Though I tried for a quiet long time, I will directly post how to do it.

The output matches one of the SSHFP records.

It's not the sha1 on base64 encoded public key or the hex string it self, but you need to firstly unhexlify the hex string. Feel free to copy and paste the code and print out the unhexlified hex string.

Conclusion: To match SSHFP, we do need public key's raw bytes being in form of hex string. Unhexlify and hash it, you'l get the output same with SSHFP.