An OpenSSH public key file, such as id_ed25519.pub, is one line of text.
The same line, with optional options in front, is a line of
authorized_keys. The key itself is in the SSH wire encoding of RFC 4253,
base64-encoded (OpenSSH PROTOCOL, section 5.1). Sources: RFC 4251 section
5 (data types), RFC 4253 section 6.6, RFC 5656 section 3.1, RFC 8709 section 4, RFC 4716
section 3, OpenSSH’s sshd(8), ssh-keygen(1),
PROTOCOL, PROTOCOL.key and PROTOCOL.u2f, the
OpenSSH source files named below, the OpenSSH release notes, and
draft-miller-ssh-cert for certificates.
The key line
[options] key-type base64-key [comment]
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… alice@laptop
| Field | Meaning |
|---|---|
| Options | Only in authorized_keys: comma-separated, no spaces except inside double quotes (sshd(8)). See options. |
| Key type | The algorithm name, which must match the type inside the encoded key, or OpenSSH rejects the line with a key type mismatch (sshkey_read in sshkey.c). See key types. |
| Key | Base64 of the key in wire encoding. |
| Comment | Free text after the key, not used for anything by sshd; ssh-keygen initialises it to user@host (ssh-keygen(1)). |
In authorized_keys, empty lines and lines starting # are
ignored, and lines may be up to 8 kilobytes long (sshd(8), “AUTHORIZED_KEYS
FILE FORMAT”).
Wire encoding
Values are a series of (RFC 4251 section 5):
- uint32: 4 bytes, big-endian.
- string: a uint32 length, then that many bytes, with no terminating null.
- mpint: a string holding a two’s complement big-endian number,
with no unneeded leading
00orFFbytes; a positive number whose top bit is set gets a leading00, and zero is an empty string.
Every key starts with its type as a string, which is why every Ed25519 key’s base64
starts AAAAC3NzaC1lZDI1NTE5: 00 00 00 0B and
ssh-ed25519.
Key types
| Key type | Encoding after the type string | Defined in |
|---|---|---|
ssh-rsa | mpint e (public exponent), mpint n (modulus) | RFC 4253 section 6.6 |
ssh-dss | mpint p, q, g, y. OpenSSH removed DSA support in OpenSSH 10.0 (release notes). | RFC 4253 section 6.6 |
ecdsa-sha2-nistp256, -nistp384, -nistp521 | string curve identifier (nistp256…, section 6.1), string Q (the public point encoded as in SEC1 section 2.3.3; point compression may be used) | RFC 5656 section 3.1 |
ssh-ed25519 | string of the 32-byte public key | RFC 8709 section 4 |
ssh-ed448 | string of the 57-byte public key. Not among OpenSSH’s key types (enum sshkey_types in sshkey.h). | RFC 8709 section 4 |
sk-ecdsa-sha2-nistp256@openssh.com | string curve name, ec_point Q, then string application (“user-specified, but typically ssh:”): a FIDO security key | OpenSSH PROTOCOL.u2f |
sk-ssh-ed25519@openssh.com | string public key, then string application | OpenSSH PROTOCOL.u2f |
ssh-mldsa44-ed25519 | A hybrid post-quantum key combining ML-DSA-44 and Ed25519: one string of 1,344 bytes, the 1,312-byte ML-DSA-44 public key followed by the 32-byte Ed25519 public key (ssh-mldsa-eddsa.c and crypto_api.h in OpenSSH). Experimental with an @openssh.com suffix in OpenSSH 10.4, enabled under this name in OpenSSH 10.6 (release notes). | draft-miller-sshm-mldsa44-ed25519-composite-sigs |
…-cert-v01@openssh.com | An OpenSSH certificate: the key type, a nonce, the key’s own fields, then serial number, certificate role, identifier, principals, valid after, valid before, critical options, extensions, reserved, the signing CA key and the signature | draft-miller-ssh-cert section 2.1, which OpenSSH’s PROTOCOL (section 1.3) refers to |
authorized_keys options
From sshd(8), “AUTHORIZED_KEYS FILE FORMAT”; option keywords are
case-insensitive.
| Option | Effect |
|---|---|
restrict | Turns on every restriction: no forwarding of ports, agent or X11, no terminal, no ~/.ssh/rc. |
agent-forwarding, port-forwarding, pty, user-rc, X11-forwarding | Turn back on what restrict turned off. |
no-agent-forwarding, no-port-forwarding, no-pty, no-user-rc, no-X11-forwarding | Turn off one feature. |
command="…" | Run this command whatever the client asks for. |
environment="NAME=value" | Set an environment variable (only if the server’s PermitUserEnvironment allows it). |
from="pattern-list" | Accept the key only from matching host names or addresses (CIDR allowed). |
expiry-time="timespec" | Stop accepting the key after this time. |
permitopen="host:port" | Limit local (-L) forwarding to these destinations. |
permitlisten="[host:]port" | Limit remote (-R) forwarding to these listen addresses. |
cert-authority | The key is a CA trusted to sign user certificates. |
principals="…" | With cert-authority: names a certificate must carry. |
tunnel="n" | Force this tun device on the server. |
no-touch-required | Do not require a touch on a FIDO security key. |
verify-required | Require user verification (such as a PIN) on a FIDO key. |
RFC 4716 public key files
Other SSH implementations write public keys in the SECSH format of RFC 4716, and it
is the default export format of ssh-keygen -e
(ssh-keygen(1)):
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "alice@laptop"
AAAAC3NzaC1lZDI1NTE5AAAAI…
---- END SSH2 PUBLIC KEY ----
Header lines are Tag: value before the base64 (RFC 4716 section 3.3);
tags are case-insensitive and at most 64 bytes, values at most 1024 bytes.
Subject (the login name) and Comment are defined, and tags
starting x- are for private use (sections 3.3.1 to 3.3.3); readers must
ignore tags they do not know. Every line is at most 72 bytes, not counting the line
ending (section 3), and a header continues on the next line when it ends with a
backslash. The base64 is the same wire-encoded key (section 3.4).
The matching private key
OpenSSH stores the private key (id_ed25519 without
.pub) as -----BEGIN OPENSSH PRIVATE KEY----- around base64 of
this structure (PROTOCOL.key; details from sshkey.c). It has
been ssh-keygen’s default since OpenSSH 7.8 (PROTOCOL
section 5.2).
| Field | Type | Meaning |
|---|---|---|
| Magic | bytes | openssh-key-v1 and a zero byte (sshkey.c writes sizeof(AUTH_MAGIC) bytes, which includes the C string’s terminator). |
| Cipher name | string | none, or a cipher when protected by a passphrase; aes256-ctr by default (DEFAULT_CIPHERNAME). |
| KDF name | string | none or bcrypt. |
| KDF options | string | Empty for none; for bcrypt a string salt and a uint32 number of rounds. ssh-keygen uses a 16-byte salt and 32 rounds by default (SALT_LEN, DEFAULT_ROUNDS; ssh-keygen -a). |
| Number of keys | uint32 | OpenSSH writes 1 and rejects any other number when reading. |
| Public keys | string each | Each public key in wire encoding. |
| Private section | string | Encrypted (or plain) list: two uint32 check numbers holding the same random value, then for each key its private key fields (encoded as for the SSH agent) and a comment string, then padding bytes 1, 2, 3 … until the length is a multiple of the cipher’s block size. The check numbers match only if decryption worked, that is, if the passphrase was right. |