The OpenSSH Private Key Format
Traditionally OpenSSH has used the OpenSSL-compatible formats PKCS#1 (for RSA) and SEC1 (for EC) for Private keys.
This week I discovered that it now has its own format too,
which is the default output format for some installations of
After peeking at the binary I found, much to my dismay - and very much unlike the ssh public key format (RFC 4253) - that OpenSSH private key format is not intuitively obvious, I headed to les googles.
I searched high and low (or at least past page 2, which is a distinguished mark of true dedication), but found no useful information to assauge my curiosity (and habit).
In lieu of the docs I turned to the source. With a combination of the concentrated efforts of my best code sluething and reverse engineering skills, I believe I have (here below) produced the most complete documentation the Internet has to offer on the subject.
So, without further ado...
OpenSSH Private Keys
On the outside it's PEM encoded. It looks like this:
-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR9WZPeBSvixkhjQOh9yCXXlEx5CN9M yh94CJJ1rigf8693gc90HmahIR5oMGHwlqMoS7kKrRw+4KpxqsF7LGvxAAAAqJZtgRuWbY EbAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA 6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/ EAAAAgBzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKwAAAAOYWpAYm93aWUubG9j YWwBAg== -----END OPENSSH PRIVATE KEY-----
But, unlike most PEMs, there's no DER inside.
Instead it's the "proprietary" OpenSSH format, which looks like this:
"openssh-key-v1"0x00 # NULL-terminated "Auth Magic" string 32-bit length, "none" # ciphername length and string 32-bit length, "none" # kdfname length and string 32-bit length, nil # kdf (0 length, no kdf) 32-bit 0x01 # number of keys, hard-coded to 1 (no length) 32-bit length, sshpub # public key in ssh format 32-bit length, keytype 32-bit length, pub0 32-bit length, pub1 32-bit length for rnd+prv+comment+pad 64-bit dummy checksum? # a random 32-bit int, repeated 32-bit length, keytype # the private key (including public) 32-bit length, pub0 # Public Key parts 32-bit length, pub1 32-bit length, prv0 # Private Key parts ... # (number varies by type) 32-bit length, comment # comment string padding bytes 0x010203 # pad to blocksize (see notes below)
As you can see (maybe) there's
- A format ID prefix
- Encryption headers
- An unused number for number of keys in the block
- An rfc4253-style ssh public key
- An private key somewhat modeled after the rfc4253 style
- A comment
- Padding for aligning private key to the blocksize
Note that the
blocksize is 8 (for unencrypted keys, at least).
The RFC 4253 SSH Public Key format, is used for both the embedded public key and embedded private key key, with the caveat that the private key has a header and footer that must be sliced:
- RSA private keys swap
- 8 bytes of unused checksum bytes as a header
- n bytes (between 0 and 7) of padding
- bytes > 0x00 and < 0x08 must be trimmed (from the right)
- the padding must be a (right-trimmed) substring of
- (that includes the empty substring)
- if the last byte isn't padding, it's part of the comment (0x21 to 0x7e)
The canonical source code is only available via tarball (.tar.gz).
However, there's also a well-maintained fork (Portable OpenSSH) which has perfectly linkable source code and among them I found this to be the file of greatest interest:
Even more particularly, these were the most interesting functions:
I don't quite remember where, but another piece of information I
discovered is that when the key isn't encrypted (cipher and kdf
values are "none" and "none") the
blocksize is 8 bytes and the
value of CLFLAG_NONE is also 8:
blocksize = 8 CFLAG_NONE = 00001000
By AJ ONeal
Did I make your day?
Buy me a coffee