How to Use SSH Keys for Secure Passwordless Connections
Introduction
I set up HTTP/SSH authentication for Git so long ago that I’ve forgotten the details. While working on server connections recently, I decided to revisit the underlying principles and security mechanisms, and document them properly.
Why Not Just Use a Password?
SSH supports password-based login, but password authentication has several inherent problems:
- The password is transmitted: Even though the connection is encrypted, the password itself is still sent to the server, giving the server access to it.
- It is vulnerable to brute-force attacks: Machines with port 22 exposed to the internet receive large numbers of automated dictionary-attack attempts every day.
The core problem solved by SSH key authentication is: how to prove that you possess a secret without transmitting the secret itself.
Fundamentals of Asymmetric Cryptography
An SSH key is an asymmetric cryptographic key pair consisting of two mathematically related keys:
- Private Key:
~/.ssh/id_ed25519. It always remains on your computer and must never be disclosed. - Public Key:
~/.ssh/id_ed25519.pub. It can be shared freely and copied to any server you want to access.
The key property of this pair is that the private key can generate a signature for some data, and anyone can use the public key to verify that the signature was genuinely produced by the corresponding private key. However, the private key cannot be derived from the public key.
What Happens During an SSH Connection
A complete connection can be divided into two stages: first establishing an encrypted channel, then performing authentication.
Stage One: Establishing an Encrypted Channel
Before any authentication takes place, the Client and Server first negotiate an encrypted channel:
- Both sides exchange lists of supported algorithms for key exchange, encryption, MAC, and compression
- Through a Diffie-Hellman key exchange, or the now-common ECDH, both sides independently derive a symmetric key known only to them (the Session Key), without that key ever being transmitted over the network
- This process also generates a unique Session ID, which is used during the subsequent authentication stage
- All subsequent packets are protected using symmetric encryption with this Session Key, such as AES-GCM or ChaCha20-Poly1305
Host Key: Are You Really Connecting to the Right Machine?
During the key exchange, the Server also signs the exchange using its own private key to prove its identity. This is why you see the following prompt the first time you connect:
The authenticity of host 'example.com (93.184.216.34)' can't be established.ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.Are you sure you want to continue connecting (yes/no/[fingerprint])?After you enter yes, the host’s public-key fingerprint is recorded in ~/.ssh/known_hosts. If the fingerprint does not match on a subsequent connection, SSH immediately refuses the connection and displays a warning because this may indicate a man-in-the-middle attack.
Stage Two: Public-Key Authentication
Only after the encrypted channel has been established does the client prove who is logging in:
- Client: I want to log in using this public key. Do you accept it? (Only the public key is sent.)
- Server: Check the user’s
~/.ssh/authorized_keysfor this public key. If it is not present, reject the request. - Client: Use the private key to generate a signature over data containing the Session ID, username, service name, and other information.
- Server: Use the public key from
authorized_keysto verify the signature. Successful verification means the other party possesses the corresponding private key, so the login succeeds.
There are two key design features:
- The private key never leaves the local machine at any point. Even if the server is compromised, only the public key can be exposed.
- The signature is bound to the Session ID. Every connection has a different Session ID, so an attacker cannot reuse an intercepted signature in a replay attack against another connection.
Practical Steps
Generating a Key
OpenSSH, available on all major operating systems, provides ssh-keygen and several other tools for generating and managing key pairs.
ssh-keygen -t ed25519 -C "Comment appended to the public key"Although many tutorials use -t rsa -b 4096, Ed25519 is now the preferred choice: its keys are shorter, signatures are faster, its security strength is equivalent to RSA-3072 or better, and it avoids the risk of incorrectly configured parameters associated with RSA.
Two files are generated:
~/.ssh/id_ed25519 # Private key; never disclose it~/.ssh/id_ed25519.pub # Public key; safe to shareYou will be prompted for a passphrase during the process, and you should always set one. When a passphrase is configured, the private-key file is stored in encrypted form, and the passphrase is required to unlock it. If your laptop is stolen or the private-key file is leaked, an attacker still cannot use it without the passphrase. If you simply press Enter and leave it blank, the private key is stored unencrypted, so obtaining the file is effectively equivalent to gaining access to the account.
Using ssh-agent to Avoid Entering the Passphrase Every Time
Entering the passphrase for every connection becomes inconvenient. ssh-agent is a background process that keeps the decrypted private key in memory and performs signatures on your behalf:
eval "$(ssh-agent -s)"ssh-add ~/.ssh/id_ed25519On macOS, you can store the passphrase in Keychain and configure ~/.ssh/config to add the key to the agent automatically the first time it is used:
Host * IgnoreUnknown UseKeychain UseKeychain yes AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519Installing the Public Key on a Remote Server
authorized_keys is a plain-text file containing one public key per line. Pasting keys manually is error-prone due to line breaks and permissions, so using ssh-copy-id is recommended:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostnameIt logs in once using an existing authentication method, usually a password, then appends the public key to the remote ~/.ssh/authorized_keys file and corrects its permissions.
Connecting
ssh -i ~/.ssh/id_ed25519 user@hostnameRather than entering a long command every time, it is more practical to add the configuration to ~/.ssh/config:
Host myserver HostName 93.184.216.34 User ubuntu Port 22 IdentityFile ~/.ssh/id_ed25519You can then simply run ssh myserver. scp, rsync, and git also read this configuration.
File Permissions
SSH enforces permissions very strictly. If they are too permissive, SSH refuses to use the key because other users on the same machine might be able to read it:
chmod 700 ~/.sshchmod 600 ~/.ssh/id_ed25519chmod 644 ~/.ssh/id_ed25519.pubchmod 600 ~/.ssh/authorized_keysA typical error message looks like this:
Permissions 0644 for '~/.ssh/id_ed25519' are too open.It is required that your private key files are NOT accessible by others.Using SSH with GitHub
GitHub’s SSH authentication uses the same mechanism. The only difference is that GitHub does not provide shell access; instead, it uses the public key to determine which account issued a push:
- Paste the contents of
~/.ssh/id_ed25519.pubinto Settings → SSH and GPG keys on GitHub. - Test the connection:
ssh -T git@github.com# Hi username! You've successfully authenticated, but GitHub does not provide shell access.- When cloning repositories, use the SSH URL (
git@github.com:user/repo.git) instead of the HTTPS URL.
When debugging, -v, which can be repeated up to -vvv, prints the complete negotiation and authentication process. This makes it easy to see which keys were attempted and why they were rejected:
ssh -vT git@github.comSummary
- An SSH connection has two stages: first, key exchange establishes a symmetrically encrypted channel; then authentication takes place.
- The Host Key verifies that the server is genuine, while public-key authentication verifies that you are genuine.
- The signature is bound to the Session ID, preventing replay attacks.
- Prefer Ed25519 for new keys, always set a passphrase, and use it with
ssh-agent. - A leaked private key is equivalent to a compromised account. If this happens, immediately remove the corresponding public key from every
authorized_keysfile and from GitHub, then generate a new key pair.
Further Reading
- Symmetric encryption and asymmetric encryption
- SSH Keys - RobEdwards
- Cloning with SSH URLs - GitHub Docs
- Generating a new SSH key and adding it to the ssh-agent - GitHub Docs