How SSH authentication works¶
When you type ssh alice@server.example.com, two separate questions have to be answered before you get a shell.
The first is whether you are talking to the right server.
The second is whether the server should let you in as alice.
SSH answers them in that order, with different mechanisms, and most confusing SSH behaviour (the "authenticity of host can't be established" prompt, the "REMOTE HOST IDENTIFICATION HAS CHANGED" warning, Permission denied (publickey)) makes sense once you see which question is being answered at that moment.
This page explains how the protocol fits together, what the pieces you manage as an administrator (host keys, known_hosts, authorized_keys, the agent) are for, and why public keys are a better way to log in than passwords.
Background¶
SSH was written in 1995 by Tatu Ylönen at the Helsinki University of Technology, after a password-sniffing attack on the university network.
It replaced telnet, rlogin and rsh, which sent everything, passwords included, across the network in clear text, and which trusted clients largely on the basis of their IP address.
The first protocol version had design flaws. OpenSSH removed server support for it in version 7.4 (2016) and dropped it entirely in 7.6 (2017).
The protocol in use today, SSH-2, was standardised by the IETF in 2006 in RFCs 4251 to 4254.
OpenSSH, developed by the OpenBSD project since 1999, is by far the most common implementation, and it's the one every distribution in this knowledge base ships. It also adds extensions of its own, such as certificates and the agent protocol, which are documented in the OpenSSH source tree and in IETF drafts rather than in the original RFCs.
Three layers¶
SSH-2 is defined as three protocols stacked on top of a TCP connection (normally to port 22):
| Layer | RFC | Job |
|---|---|---|
| Transport | 4253 | Agree on algorithms, perform a key exchange, authenticate the server, and then encrypt and integrity-protect everything that follows. |
| User authentication | 4252 | Authenticate the user to the server, over the already encrypted transport. |
| Connection | 4254 | Multiplex channels over the one connection: interactive shells, remote commands, SFTP, port forwarding, agent forwarding, X11. |
The order matters. By the time the server asks who you are, the connection is already encrypted, and the client has already decided to trust the server's identity. Everything about user authentication builds on that.
sequenceDiagram
participant C as Client (ssh)
participant S as Server (sshd)
C->>S: TCP connection to port 22
C->>S: Version string (SSH-2.0-OpenSSH_…)
S->>C: Version string
C->>S: KEXINIT: algorithms the client supports
S->>C: KEXINIT: algorithms the server supports
C->>S: Ephemeral key-exchange public value
S->>C: Ephemeral value + host public key + signature over the exchange hash
Note over C: Check the host key against known_hosts<br/>and verify the signature
C->>S: NEWKEYS (everything after this is encrypted)
S->>C: NEWKEYS
C->>S: Request the ssh-userauth service
C->>S: Authentication attempts (public key, password, …)
S->>C: USERAUTH_SUCCESS
C->>S: Open a session channel, request a shell or command
The transport layer¶
Negotiating algorithms¶
Each side sends a list of the algorithms it supports, in order of preference, for each job: key exchange, host key type, cipher, message authentication code (MAC) and compression. For each job, the first algorithm in the client's list that the server also supports is chosen. This is why a server administrator limits algorithms by removing them from the server's lists, while the client decides which of the remaining ones is actually used. It is also why old and new software can still talk to each other: as long as there is one algorithm in common, the connection works.
On RHEL, AlmaLinux and Rocky Linux the server's lists come from the system-wide crypto policy rather than from OpenSSH's built-in defaults, so that one setting governs OpenSSH, TLS libraries and other software together.
Key exchange¶
The key exchange lets two parties who share no secret agree on one over a network that anyone might be watching.
Modern SSH uses elliptic-curve Diffie-Hellman (curve25519-sha256), or a hybrid that combines it with a post-quantum algorithm, so that the shared secret stays safe even against an attacker who records the traffic today and gets a large quantum computer later.
OpenSSH 9.0 made the hybrid sntrup761x25519-sha512@openssh.com its default (later releases also accept the name without the suffix), and OpenSSH 10.0 switched the default to mlkem768x25519-sha256, which uses the ML-KEM algorithm standardised by NIST.
All of the labs used for this knowledge base list mlkem768x25519-sha256 first.
Both sides generate fresh, throwaway key-exchange values for every connection. From the shared secret and a hash of everything exchanged so far (the exchange hash), each side derives the encryption and MAC keys. The exchange hash of the first key exchange also becomes the session identifier, which comes back later in user authentication.
Because the key-exchange values are thrown away afterwards, someone who later steals the server's long-term keys still can't decrypt recorded sessions. This property is called forward secrecy.
After the exchange, traffic is protected by an authenticated cipher such as chacha20-poly1305@openssh.com or aes256-gcm@openssh.com.
The keys are renegotiated from time to time during long connections.
Host keys: proving the server's identity¶
Diffie-Hellman on its own doesn't tell you who you agreed a secret with. An attacker sitting between you and the server could run one key exchange with you and another with the server, and relay everything (a man-in-the-middle attack).
To prevent this, the server has a long-term host key pair, generated when OpenSSH is installed or the machine first boots, and stored in /etc/ssh/ssh_host_*_key.
During the key exchange the server sends its host public key and signs the exchange hash with the host private key.
The client checks the signature.
A valid signature proves that whoever is on the other end of this particular key exchange holds the private key belonging to that public key.
A relaying attacker can't produce it, because each of their two key exchanges has a different exchange hash, and they don't have the server's private key.
The signature only moves the question, though: it proves possession of a key, not that the key belongs to the server you meant.
That is the job of known_hosts.
Trust on first use and known_hosts¶
OpenSSH keeps a list of host names and the host public keys it has accepted for them, in ~/.ssh/known_hosts for each user and /etc/ssh/ssh_known_hosts for the whole system.
There are three cases:
- The key matches the stored one. The connection continues silently.
- The host isn't in the file. By default,
sshshows the key's fingerprint (a SHA-256 hash of the public key, written in Base64, such asSHA256:Xh1…) and asks whether to continue. If you accept, it saves the key. This is trust on first use (TOFU): the first connection is taken on faith, and every later connection is checked against it. - The host is listed with a different key.
sshrefuses to connect and prints a prominent warning that the remote host identification has changed. Either the server was reinstalled or its keys were regenerated, or someone is intercepting the connection. SSH can't tell which, so it makes you decide.
TOFU is a pragmatic compromise.
It protects every connection after the first one, and it needs no infrastructure at all, which is a large part of why SSH was adopted so quickly.
Its weakness is the first connection: answering "yes" without comparing the fingerprint with one obtained some other way (from the server's console, from the provisioning system, from a colleague) accepts whatever key is presented.
The StrictHostKeyChecking client option controls this behaviour: ask (the default) prompts, accept-new saves unknown keys without asking but still refuses changed ones, and yes refuses unknown hosts entirely.
Some distributions, including Debian and Ubuntu, set HashKnownHosts yes in the system client configuration.
Host names in known_hosts are then stored as salted hashes, so that a stolen known_hosts file doesn't hand an attacker a list of other machines to try.
The file still works for checking, because ssh hashes the name you type and looks for a match.
There are ways to avoid relying on the first connection:
- SSHFP DNS records (RFC 4255) publish host key fingerprints in DNS. They are only trustworthy when the zone is signed with DNSSEC and the resolver validates it.
- Host certificates: a certificate authority signs each server's host key, and clients trust the CA (an
@cert-authorityline inknown_hosts) instead of each host key individually. - Distributing
ssh_known_hostsfrom a configuration management system, so users never see the prompt. - Host key rotation: with
UpdateHostKeys, a server can tell an already-authenticated client about its other host keys, so that a new key can be learned while the old one is still trusted.
User authentication¶
Once the transport is up, the client asks for the ssh-userauth service and tries to authenticate as a particular user name.
The server answers each failed attempt with the list of methods that can continue.
That list is what you see in the familiar error message: Permission denied (publickey,password) means the server would have accepted a public key or a password, and the client ran out of things to try.
A server can require more than one method in sequence (OpenSSH's AuthenticationMethods, for example a public key and then a one-time code), and it limits the number of attempts per connection (MaxAuthTries).
The methods themselves are described below.
Public key¶
The user has a key pair.
The private key stays on the client, usually in ~/.ssh/id_ed25519, ideally encrypted with a passphrase.
The public key is copied to the server and listed in the user's ~/.ssh/authorized_keys.
Authentication happens in two stages:
- The client may first ask, without proving anything, whether the server would accept a given public key. This lets it try several keys cheaply.
- To actually authenticate, the client signs a message containing the session identifier, the user name and the public key, and sends the signature. The server checks the key against
authorized_keysand verifies the signature.
Two properties follow from this design:
- The private key never leaves the client. The server only ever learns the public key, which isn't secret. A compromised server can't collect anything that lets it log in elsewhere as you.
- The signature is bound to this connection. Because it covers the session identifier, which is unique to this key exchange, a signature captured or relayed by a man-in-the-middle is useless on any other connection. An attacker who tricked you into accepting their host key still can't use your key to log in to the real server as you.
authorized_keys lines can also carry restrictions in front of the key, such as from="198.51.100.0/24" (only from these addresses), command="…" (only this command) or restrict (no forwarding, no terminal).
That makes a key a much more precise grant than a password.
Before accepting a key, OpenSSH (with StrictModes, the default) checks that the user's home directory, ~/.ssh and authorized_keys aren't writable by anyone other than the user and root.
Otherwise another local user could add their own key and log in as that user.
Wrong ownership or modes are the most common reason a correctly copied key is silently ignored.
Public keys can also live on hardware.
Since OpenSSH 8.2, FIDO2 security keys can hold sk-ssh-ed25519 and sk-ecdsa keys: the private key can't be copied off the device, and each signature can require a touch.
Password¶
The client sends the password to the server, inside the encrypted transport, and the server checks it, usually through PAM.
Encryption protects the password from eavesdroppers on the network, but not from the server itself. The server sees the password in clear text, so a password typed into a server that is compromised, or into an impostor whose host key you accepted, is captured and can be replayed anywhere it is valid. Unlike a public key signature, a password isn't tied to the connection it was sent on.
Keyboard-interactive¶
Keyboard-interactive (RFC 4256) is a general challenge-and-response method: the server sends one or more prompts, and the client shows them to the user and returns the answers.
On Linux, sshd passes these conversations to PAM, so this method can carry anything a PAM stack asks for: a password, a one-time code from an authenticator app, or a combination.
This has a practical consequence that often surprises administrators.
With UsePAM yes, an ordinary password can be accepted through keyboard-interactive even when PasswordAuthentication is no.
Turning off password logins therefore means turning off both methods, unless keyboard-interactive is deliberately kept for a second factor.
Certificates¶
Plain public keys have a scaling problem: every server needs every authorised user's public key in the right authorized_keys file, and removing someone means editing all of them.
OpenSSH certificates (not X.509; a simpler format of OpenSSH's own) add a certificate authority.
The CA signs a user's public key together with a list of principals (user names the certificate is valid for), a validity period, and permitted features such as port forwarding.
Servers are configured to trust the public key of the CA (TrustedUserCAKeys) instead of individual user keys.
Short-lived certificates, issued after a single sign-on login for example, make removing access largely automatic: a certificate that expires tonight doesn't need to be revoked.
Key revocation lists (RevokedKeys) cover the cases where it does.
The same mechanism works in the other direction for host keys, as mentioned above. Certificates are still public key authentication on the wire; they change how trust is distributed, not how the signature is made.
Other methods¶
gssapi-with-micauthenticates with an existing Kerberos ticket, common in Active Directory and FreeIPA environments. RHEL-family distributions enable it in their default configuration.hostbasedtrusts a client machine (by its host key) to vouch for its users, an improved descendant ofrsh's trust model. It's rarely used and off by default.noneis the method a client tries first to learn which methods the server offers. It only succeeds for accounts that need no authentication at all.
What the agent does¶
A passphrase protects the private key file at rest, but typing it for every connection quickly becomes tiresome.
That is the problem ssh-agent solves.
The agent is a small background process that holds decrypted private keys in memory.
You unlock a key once with ssh-add.
Clients find the agent through a Unix socket named in the SSH_AUTH_SOCK environment variable.
When ssh needs a public key signature, it sends the data to be signed to the agent, and the agent sends back only the signature.
The agent never hands out the private key itself, not even to ssh.
Many desktop sessions start an agent automatically (current GNOME desktops provide one through gcr-ssh-agent, and gpg-agent can act as one).
On a server or in a plain console session you start it yourself, which is why the familiar eval "$(ssh-agent -s)" exists: the agent prints the shell commands that set SSH_AUTH_SOCK and SSH_AGENT_PID, and eval runs them in your current shell.
Agent forwarding¶
With agent forwarding (ssh -A), the agent's socket is made available on the remote host, so that from there you can log in to a third machine with keys that never left your workstation.
It is convenient, but it carries a real risk: for as long as you're connected, anyone with root access on the intermediate host can use your forwarded socket to authenticate as you, anywhere your keys are accepted.
They can't copy the key, but they don't need to.
Safer alternatives exist for the most common reason to forward an agent, hopping through a bastion host: ssh -J (ProxyJump) makes the connection to the final server through the bastion, with authentication still done end-to-end from your workstation.
When forwarding is needed, ssh-add -c asks for confirmation before each use of a key, and since OpenSSH 8.9 ssh-add -h can restrict a key to particular destinations.
Why keys beat passwords¶
Putting the pieces together:
- Nothing reusable crosses the network. A password is revealed to every server you type it into. A key signature is useless anywhere else, and the private key stays with you.
- Man-in-the-middle resistance. If you accept a wrong host key, a password is captured outright. A public key signature is bound to the attacker's session and can't be relayed to the real server.
- No guessing. An Ed25519 key offers a strength of roughly 128 bits; no online guessing attack will ever find it. Passwords that people can remember can be guessed, and every server with password login enabled on the internet receives a constant stream of attempts.
- No reuse across systems. Users reuse passwords; a breach elsewhere becomes a breach of your server. A key pair is generated for this purpose.
- Precise and revocable. Each key can be limited (
from=,command=,restrict), and removing one line fromauthorized_keys(or letting a certificate expire) revokes one key without affecting anyone else. - Two factors are natural. An encrypted private key is something you have plus something you know; a FIDO2 key adds a physical device and a touch.
Keys aren't free of problems.
A private key file without a passphrase is as good as a password written on a note: anyone who copies it can use it.
authorized_keys files tend to accumulate old keys that nobody remembers adding, and plain keys don't expire.
Organisations with many servers and users usually answer this with certificates, central key distribution or both.
These are problems of managing keys, though; the authentication mechanism itself is much stronger than a password.
Related¶
- Tutorial: Log in with SSH keys
- How-to: How to harden the SSH server
- Reference: OpenSSH server configuration directives
Further reading¶
- RFC 4251 (architecture), RFC 4252 (user authentication), RFC 4253 (transport) and RFC 4254 (connection)
- RFC 4256: keyboard-interactive authentication
- RFC 4255: SSHFP DNS records
- RFC 8709 (Ed25519 keys in SSH) and RFC 8731 (Curve25519 key exchange)
- OpenSSH protocol notes:
PROTOCOL.certkeysandPROTOCOL.agent - OpenSSH release notes, for when each feature and default changed
ssh(1),sshd(8),ssh-agent(1),ssh-add(1),ssh-keygen(1)andsshd_config(5)man pages