Everything you always wanted to know about OpenSSH RSA keys but did not dare to ask
Hey! It’s 5 in the morning, I can’t sleep, and Github is asking me to check the fingerprint of one of my SSH keys, so now I’m wondering : "What the hell is this fingerprint by the way?". Yeah, weird questions always pop up in the most unexpected times and places. But nevermind, let’s find out. Hopefully it’s not 5am anymore on your side of the screen.
Generate test data
Rather than do some testing on your real, used, important SSH keys, you may want to create a pair for the occasion. So let’s do it.
$ cd ~ $ mkdir -p workspace/sshtest $ cd workspace/sshtest $ ssh-keygen -t rsa -b 2048 -f sshkey -N "" Generating public/private rsa key pair. Your identification has been saved in sshkey. Your public key has been saved in sshkey.pub. The key fingerprint is: d8:39:38:b3:e9:47:54:72:b8:ea:40:32:49:b9:99:72 lertsenem@gimli The key's randomart image is: +--[ RSA 2048]----+ | . . | | o o o | | . = = | |. E . +o. | | o + =oS | | . .=.. | | oo. | | .. . | | .. | +-----------------+
(I will assume in all the following code excerpts that we are in this workspace/sshtest directory.)
We now have two files :
-
sshkey, which is the private key;
-
sshkey.pub, which is the public key.
If you don’t know what a public or private key is, google “asymetric cryptography” and come back later. It’s a very very interesting subject, but it’s way beyond the scope of this entry.
The private key
Let’s see that private part (pun very much intended).
$ cat sshkey -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAuT1+sz2OJzlEh8sxIPCgQdhAUz6WP/lFC8Zytj2AV6iR/N2u ywsED3GFwl3geZdnIklrEMEp7M3C7Tss+eqLoDKieEWDXgrR07dysSgne1Z7AlrT J15dT27CT9MBNPdJ3VgB6rEdO3FRZqtcNEsS9d7V0lEHcTv+xPYJz7vfF2Q9+omF hHxgZG/jNp/0Sf5mDbo0KPeEcf8eS7qu5cFecFkcS83krOx2lz42Lxhj4BoW2QrH uTbllS2fbQJvfu+6Fix7Y1PYpzykQR7FTUKInZ4Zw9S0Ukh+l1e7UV48QO+Zy+wK r/oH4X/PQp2ttETfHeSwkG561fzSxNV9KKJUqQIDAQABAoIBAHXhDUSw0kphphuo EV594e2k2OJgfKq/TNH+ejdAkfC2hBhfVgE9xFqzikQzC0s1LtthzzE9Sxm3oujm dGuhdzL9pehVjBCHYBmTrvfPrrhNQ0AvXhm6cjS/LgYwJ592Elo5zBZ2mTyfkwZ7 29QnRiySno0VJWBIporea0X3rzMpcCpvzyzDz7TVmSjoSVeWh68xYK/bcg3CcnqE 9WpGE2MblAB6Qzdu7nH616ux5mfNjCjPaeW/efcIrSVd2+OHhoGsHlB0ClPKivBP bDuc7y29LjEbwfVdzdZvuASwBC/FRFdBaqtDl3hy8MJh0bZNygeFil/HbMzFLE19 K1Biy4ECgYEA7Mji3+7f+4GaOwAF+SWxcm6//R7NXxczcHDccEuy1jEWD0kVw6+k 6WYxYFJI/r6mJtoIUTeSxwXYaf3eiVZYmFozZQ1nHrw7a9Kpdelx4g/X3iSDS3HF i1XnzRyPOJLkCjWzDbpQwTFPZN9pAqhBbh6FsqTsQY32x0H4JAjzZ9ECgYEAyEXK gmuf6iO9Wvo4zzK217rjW0ROaW8S26WzgOtLe2RseMThPtRr9XbfRDPH7I6M1CgQ x4Nk9R3i0cWGdgVPijnqdz0ByDo+3QnXn/F7JpiAgf2aeV0lZoUm8VQFnzb6DCfD smrb9Lwem2AwMVW89XLyVNZKJXctuQtkF6LrrVkCgYEAu9mvrAqd5bFvQ2mEU6Hk wi084TpDojIM3CKdr/VOPwGYR64qtyGsyoIJoCoHwRpNRLtrJ8vYBU8M3yHKH9jB 1rSQ25Wjfs3Esojhais27yq0jocXJo5vM1iG2sHSmgZnJfZuQ/DRSkOtghCABdyo dA2A8jZHPMa8xl3+18/GE1ECgYA5uOvSRUTVDjIAa8g95+cNLJ9k6b4T42CzEAI5 sReTW/FrqYsmOcFYAUf+d1IzGy754wZbCsLpZxC910mW8JbpRyIN9fZyiF8gBDsy rT4KdL/tN3PzcOZbsxrzSX1JU2YkZL2hyezVj/beX43kWkrZD6VK0GyUedIi2Gaj 7WAyGQKBgBhsCBXjCQehAG3/5sUj/Hqlc2Vuix3roK1rR+7xwZUsJe7ewM3Xtm3d NLI9tD0ANAyjhBhj+n5qbJDYOy0o+PMpYqBtZFkWW2rJOm3yQNZaZZAwyK0nCrV1 +H/p/1jciJQJnU97Md+g8bdQbSk0ZPdTYJJxa5UvG/wx82QULYML -----END RSA PRIVATE KEY-----
![]() |
Don’t paste your private keys on the internet like this if you intend to use them, you’ll hurt yourself. |
Okay, so my private key seems to be in a regular PEM format. We can check with openssl to be sure :
$ openssl rsa -in sshkey -noout -check RSA key ok
Wonderful! Following standards! With full interoperability! Now for the public part.
The public key
$ cat sshkey.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5PX6zPY4nOUSHyzEg8KBB2EBTPpY/+UULxnK2PYBXqJH83a7LCwQPcYXCXeB5l2ciSWsQwSnszcLtOyz56ougMqJ4RYNeCtHTt3KxKCd7VnsCWtMnXl1PbsJP0wE090ndWAHqsR07cVFmq1w0SxL13tXSUQdxO/7E9gnPu98XZD36iYWEfGBkb+M2n/RJ/mYNujQo94Rx/x5Luq7lwV5wWRxLzeSs7HaXPjYvGGPgGhbZCse5NuWVLZ9tAm9+77oWLHtjU9inPKRBHsVNQoidnhnD1LRSSH6XV7tRXjxA75nL7Aqv+gfhf89Cna20RN8d5LCQbnrV/NLE1X0oolSp lertsenem@gimli
Arg. What the hell is that? This does not look like any standard format I know of. Well, to be fair the SSH pubkey format is kinda-de-facto-standard, but still.
Let’s analyze this calmly. Obviously, the important part is the second field. The first indicates the type (ssh-rsa here) and the last indicates the user (lertsenem@gimli). Obviously again, this second field is base64 encoded. So let’s start by decoding it.
$ cat sshkey.pub | cut -d ' ' -f 2 | base64 --decode | hd 00000000 00 00 00 07 73 73 68 2d 72 73 61 00 00 00 03 01 |....ssh-rsa.....| 00000010 00 01 00 00 01 01 00 b9 3d 7e b3 3d 8e 27 39 44 |........=~.=.'9D| 00000020 87 cb 31 20 f0 a0 41 d8 40 53 3e 96 3f f9 45 0b |..1 ..A.@S>.?.E.| 00000030 c6 72 b6 3d 80 57 a8 91 fc dd ae cb 0b 04 0f 71 |.r.=.W.........q| 00000040 85 c2 5d e0 79 97 67 22 49 6b 10 c1 29 ec cd c2 |..].y.g"Ik..)...| 00000050 ed 3b 2c f9 ea 8b a0 32 a2 78 45 83 5e 0a d1 d3 |.;,....2.xE.^...| 00000060 b7 72 b1 28 27 7b 56 7b 02 5a d3 27 5e 5d 4f 6e |.r.('{V{.Z.'^]On| 00000070 c2 4f d3 01 34 f7 49 dd 58 01 ea b1 1d 3b 71 51 |.O..4.I.X....;qQ| 00000080 66 ab 5c 34 4b 12 f5 de d5 d2 51 07 71 3b fe c4 |f.\4K.....Q.q;..| 00000090 f6 09 cf bb df 17 64 3d fa 89 85 84 7c 60 64 6f |......d=....|`do| 000000a0 e3 36 9f f4 49 fe 66 0d ba 34 28 f7 84 71 ff 1e |.6..I.f..4(..q..| 000000b0 4b ba ae e5 c1 5e 70 59 1c 4b cd e4 ac ec 76 97 |K....^pY.K....v.| 000000c0 3e 36 2f 18 63 e0 1a 16 d9 0a c7 b9 36 e5 95 2d |>6/.c.......6..-| 000000d0 9f 6d 02 6f 7e ef ba 16 2c 7b 63 53 d8 a7 3c a4 |.m.o~...,{cS..<.| 000000e0 41 1e c5 4d 42 88 9d 9e 19 c3 d4 b4 52 48 7e 97 |A..MB.......RH~.| 000000f0 57 bb 51 5e 3c 40 ef 99 cb ec 0a af fa 07 e1 7f |W.Q^<@..........| 00000100 cf 42 9d ad b4 44 df 1d e4 b0 90 6e 7a d5 fc d2 |.B...D.....nz...| 00000110 c4 d5 7d 28 a2 54 a9 |..}(.T.| 00000117
(Note for self, the base64 option of openssl is shitty, don’t use it)
Hey, it looks as if the type is encoded in this second field too: we can clearly see the ssh-rsa string at the beginning. But this does not really help us. Let’s try the other way around by looking at the standard-PEM-encoded public key file.
For this, we will generate it using the private part and openssl like this:
$ openssl rsa -in sshkey -pubout -out sshkey.pub2 writing RSA key $ cat sshkey.pub2 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuT1+sz2OJzlEh8sxIPCg QdhAUz6WP/lFC8Zytj2AV6iR/N2uywsED3GFwl3geZdnIklrEMEp7M3C7Tss+eqL oDKieEWDXgrR07dysSgne1Z7AlrTJ15dT27CT9MBNPdJ3VgB6rEdO3FRZqtcNEsS 9d7V0lEHcTv+xPYJz7vfF2Q9+omFhHxgZG/jNp/0Sf5mDbo0KPeEcf8eS7qu5cFe cFkcS83krOx2lz42Lxhj4BoW2QrHuTbllS2fbQJvfu+6Fix7Y1PYpzykQR7FTUKI nZ4Zw9S0Ukh+l1e7UV48QO+Zy+wKr/oH4X/PQp2ttETfHeSwkG561fzSxNV9KKJU qQIDAQAB -----END PUBLIC KEY-----
Hey, let’s just think for a moment: PEM format is just some kind of base64 encoding too… (of a DER encoded ASN1 structure, but nevermind that). I can decode it too:
$ cat sshkey.pub2 | grep -v -- ----- | base64 --decode | hd 00000000 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 |0.."0...*.H.....| 00000010 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 |........0.......| 00000020 00 b9 3d 7e b3 3d 8e 27 39 44 87 cb 31 20 f0 a0 |..=~.=.'9D..1 ..| 00000030 41 d8 40 53 3e 96 3f f9 45 0b c6 72 b6 3d 80 57 |A.@S>.?.E..r.=.W| 00000040 a8 91 fc dd ae cb 0b 04 0f 71 85 c2 5d e0 79 97 |.........q..].y.| 00000050 67 22 49 6b 10 c1 29 ec cd c2 ed 3b 2c f9 ea 8b |g"Ik..)....;,...| 00000060 a0 32 a2 78 45 83 5e 0a d1 d3 b7 72 b1 28 27 7b |.2.xE.^....r.('{| 00000070 56 7b 02 5a d3 27 5e 5d 4f 6e c2 4f d3 01 34 f7 |V{.Z.'^]On.O..4.| 00000080 49 dd 58 01 ea b1 1d 3b 71 51 66 ab 5c 34 4b 12 |I.X....;qQf.\4K.| 00000090 f5 de d5 d2 51 07 71 3b fe c4 f6 09 cf bb df 17 |....Q.q;........| 000000a0 64 3d fa 89 85 84 7c 60 64 6f e3 36 9f f4 49 fe |d=....|`do.6..I.| 000000b0 66 0d ba 34 28 f7 84 71 ff 1e 4b ba ae e5 c1 5e |f..4(..q..K....^| 000000c0 70 59 1c 4b cd e4 ac ec 76 97 3e 36 2f 18 63 e0 |pY.K....v.>6/.c.| 000000d0 1a 16 d9 0a c7 b9 36 e5 95 2d 9f 6d 02 6f 7e ef |......6..-.m.o~.| 000000e0 ba 16 2c 7b 63 53 d8 a7 3c a4 41 1e c5 4d 42 88 |..,{cS..<.A..MB.| 000000f0 9d 9e 19 c3 d4 b4 52 48 7e 97 57 bb 51 5e 3c 40 |......RH~.W.Q^<@| 00000100 ef 99 cb ec 0a af fa 07 e1 7f cf 42 9d ad b4 44 |...........B...D| 00000110 df 1d e4 b0 90 6e 7a d5 fc d2 c4 d5 7d 28 a2 54 |.....nz.....}(.T| 00000120 a9 02 03 01 00 01 |......| 00000126
Look at that! The bytes are looking quite similar: I can find the key decoded from the previous file in this one, starting with 01 01 00 b9 3d 7e…. There is just a different header (the OpenSSH format contains the ssh-rsa type indication) and some kind of added footer in the standard version.
If I try and generate a bunch of other ssh key the same way I did for this one, I can see that the header part is actually constant. Knowing the size and type of my key, I can then reconstruct the SSH pubkey file using the standard keyfile:
$ echo -n "ssh-rsa " > reconstructed_sshkey.pub $ echo -n "$( (echo -ne "\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00"; cat sshkey.pub2 | grep -v -- ----- | base64 --decode | head -c-5 | tail -c+31 ) | base64 --wrap=0 )" >> reconstructed_sshkey.pub $ echo " lertsenem@gimli" >> reconstructed_sshkey.pub $ diff sshkey.pub reconstructed_sshkey.pub && echo "same files" same files
Good.
The fingerprint
Now to the fingerprint thing. We can obtain it from our SSH pubkey file by using the ssh-keygen command like this:
$ ssh-keygen -lf sshkey.pub 2048 d8:39:38:b3:e9:47:54:72:b8:ea:40:32:49:b9:99:72 lertsenem@gimli (RSA)
This fingerprint is probably some kind of hash. There are 32 bytes, it looks like MD5. I could dive into the code, but I’d rather guess, it’s more fun (and faster).
First guess is to hash the whole file.
$ md5sum sshkey.pub 2a77cc671735beb0c7f7f89646eae5d1 sshkey.pub
Nope. Maybe just the important part?
$ cat sshkey.pub | cut -d ' ' -f 2 | md5sum 7894f290c9c9013b7a3729c9c4f6791c -
Nope. Well when the file is read and the key is loaded, it’s probably decoded too so let’s try that.
$ cat sshkey.pub | cut -d ' ' -f 2 | base64 --decode | md5sum d83938b3e9475472b8ea403249b99972 -
Yey! That’s our fingerprint right here! Now I don’t need ssh-keygen anymore. How cool is that?
Okay, I’ll still use it for convenience, but I could use only OpenSSL.
Yeah, I’ll still need that header part. But that’s probably only type and size of the key, plus maybe a magic number, so that doesn’t count. Still: yey.
And now I can go to sleep.