The SSH Public Key format
Published 2018-12-3Hello!
This week we're gonna dive into SSH and, to a lesser extent, OpenSSL.
Today we're going to cover everything that you wanted to know (or at least that I wanted to know) about SSH Public Keys but were too afraid to ask (well, except that you're obviously asking now) and that your parents wouldn't tell you anyway (mostly because they had no idea).
In short, the text format (RFC 4253) is like this:
id_rsa.pub
(or id_ecdsa.pub
):
[type-name] [base64-encoded-ssh-public-key] [comment]
For example:
ssh-rsa AAAAB3NzaC1yc2E...Q02P1Eamz/nT4I3 root@localhost
And the binary format looks like this:
[decoded-ssh-public-key]
:
[32-bit length] [type name]
[32-bit length] [RSA exponent or EC type name]
[32-bit length] [RSA modulus or EC x+y pair]
As to what that means, well, it's all explained below!
But First: Private Keys
Update: It used to be that OpenSSH used the same standard DER/ASN.1 formats as OpenSSL for private keys. Now, however, OpenSSH has its own private key format (no idea why), and can be compiled with or without support for standard key formats.
It's a very natural assumption that because SSH public keys (ending in .pub
)
are their own special format that the private keys (which don't end in .pem
as we'd expect) have their own special format too.
However, they're actually in the same stardard formats that OpenSSL uses.
If you want more info check this out:
Public Keys: What you see
As you (a reader of this article) have probably already found out
(hence you're here), SSH public keys are not standard OpenSSL keys,
but rather a special format and are suffixed with .pub
.
A typical id_rsa.pub
will look like this:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 root@localhost
Traditionally SSH uses RSA for keys (as seen above), which is what you'll likely see on your Macbook.
However, it's quite likely that when you're connecting to a Linux server running a newer version of OpenSSH you'll get a message about an ECDSA fingerprint the first time you connect.
The ECDSA keys are much shorter than RSA, though
just as secure, if not moreso,
and the id_ecdsa.pub
format is about the same:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= root@localhost
Here's the general format for all SSH public keys:
[type-name] [base64-encoded-ssh-public-key] [comment]
What you don't see
If you take the key apart it's actually very simple and easy to convert. It looks like this:
[decoded-ssh-public-key]
:
[32-bit length] [type name]
[32-bit length] [RSA exponent or EC type name]
[32-bit length] [RSA modulus or EC x+y pair]
Want to see on online demo?
https://coolaj86.com/demos/ssh-to-jwk/
RSA key caveats
In ASN.1 / DER format the RSA key is prefixed with 0x00
when
the high-order bit (0x80
) is set.
SSH appears to use this format.
After running thousands of automated iterations of ssh-keygen
I can say this with certainty:
- The 3rd element of the SSH key is the RSA
n
value (given) - The 1st byte (0-index) of the 3rd element always begins with
0x00
- The 2nd byte (1-index) of the 3rd element is never less that
0x90
(144 or10010000
)
Thus a 2048-bit key actually has only 2046-bits bits in its keyspace (which was already only about 256 bits in practice anyway because only probable primes are used).
I'd like to repeat this with OpenSSL to ensure that it holds true
and see how ssh-keygen converts such a number to SSH format (i.e. 0x00
padding)
if it doesn't hold true. My best guess is that it does.
I believe that the exponent is limited to a 32-bit integer, but
honestly I don't care since all practical applications use 0x10001
(that being 65537 or 10000000000000001
).
EC key caveats
The EC key is begins with 0x04
which is a throw-away byte that means
the key is in x
+y
or uncompressed format.
(compressed format is smaller, as omits the derivable y
value, but requires
more implementation details to use - namely deriving y - so it is most often
included in order to kepp things simplicity)
If it's a P-256 key then the next 32 bytes (256 bits) are the x
value and the
remaining 32 bytes are the y
value. For P-384 length of each is 48 bytes
(384 bits).
Either way the keys are padded with 0x00
up to the length of the key,
so you can strip those away (and for some formats, such as JWK, you must strip them).
Go forth and do!
From here, with the right vocabulary and a high- (and low-) level understanding,
it should be pretty easy to find examples any specific ssh-keygen
commands on
StackOverflow and even write your own parser/packer combo as I did:
ssh-parser (demo),
ssh-packer (demo).
Bonus Material!
Just a few more things, in case you're interested:
(and with any luck those will lead you further down a few rabbit holes)
By AJ ONeal
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )