Skip to content
Go back

Ed25519 SSH Keys: Ditch RSA for Good

· Updated:
By SumGuy 9 min read
Ed25519 SSH Keys: Ditch RSA for Good
Contents

Your SSH Key Is Probably Showing Its Age

If you generated your SSH key more than five years ago and haven’t touched it since, there’s a real chance it’s RSA-2048 or smaller. That’s not great. It might still work, OpenSSH will happily connect with it, but “still works” and “is a good security choice” stopped being the same thing a while back.

The answer is Ed25519. It’s smaller, faster, and doesn’t carry RSA’s baggage. Here’s why, and how to actually switch.


RSA: The Arms Race You Can’t Win

RSA was the SSH key type for decades. It’s based on the difficulty of factoring large prime numbers: the bigger your key, the harder it is to crack, in theory.

In practice, this created a key size arms race. RSA-512 was broken in 1999. RSA-1024 is considered broken today; the computational cost to factor it is within reach of well-resourced attackers. RSA-2048 is still technically acceptable, but NIST’s formal deprecation guidance recommends transitioning away from it by 2030, and it’s already disallowed in some compliance frameworks.

RSA-4096 buys you more margin, but at the cost of noticeably slower operations and larger key material. You’re not solving the problem, you’re kicking it down the road.

There’s also an implementation issue. RSA key generation needs a good source of randomness. Poor entropy, common on freshly provisioned VMs or embedded systems, can produce weak keys that look fine on the surface.


DSA: Don’t. Just Don’t

DSA (Digital Signature Algorithm) is deprecated everywhere that matters. OpenSSH disabled DSA key support by default back in OpenSSH 7.0 (2015). Most modern distros won’t even generate DSA keys anymore. If you have one, rotate it immediately. There’s no nuance here.


ECDSA: Better, But Baggage

ECDSA improved on RSA in the obvious ways: smaller keys, faster operations. But it uses NIST P-256 or P-384 curves, and those curves have a trust problem. They were designed with NSA input in the early 2000s, and while no concrete backdoor has been proven, the cryptographic community has been uneasy about them since the Dual_EC_DRBG revelations. “Probably fine” is a phrase you’d rather not apply to your authentication keys.

ECDSA also shares RSA’s weak spot: it needs high-quality randomness at signing time, not just at generation time. A bad random number generator during signing can leak your private key. This has happened in the wild, on real hardware.


Ed25519: Why It Wins

Ed25519 is based on the Curve25519 elliptic curve, designed by Daniel Bernstein with explicit, auditable parameters and no NIST involvement. The design choices are documented and reviewable, not handed down from a government agency.

Here’s why it’s the right default:


Generating an Ed25519 Key

One command, on Linux, macOS, or WSL:

Terminal window
ssh-keygen -t ed25519 -C "[email protected]"

The -C flag is just a comment: use your email, hostname, or whatever helps identify the key later. It shows up in authorized_keys and makes auditing easier down the line.

You’ll be prompted for a save location (accept the default ~/.ssh/id_ed25519 unless you have a reason not to) and a passphrase.

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/you/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/you/.ssh/id_ed25519
Your public key has been saved in /home/you/.ssh/id_ed25519.pub

Passphrase: Not Optional, Actually

When ssh-keygen asks for a passphrase, use one. Here’s what it actually protects: if your private key file is ever exfiltrated, laptop stolen, backup misconfigured, S3 bucket left public for three hours, the attacker can’t use it without the passphrase.

Your private key without a passphrase is a credential sitting in plain text. With a passphrase, it’s encrypted at rest using AES-256-CTR (OpenSSH’s key format). The passphrase never leaves your machine; it’s only used locally to decrypt the key in memory.

“But I’ll have to type it every time.” No, you won’t. That’s what ssh-agent is for.


ssh-agent: Type Your Passphrase Once

ssh-agent holds your decrypted key in memory for the rest of your session. Add this to your ~/.bash_profile or ~/.zshrc:

Terminal window
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)"
fi

Then add your key:

Terminal window
ssh-add ~/.ssh/id_ed25519

Type your passphrase once, and every SSH connection for the rest of the session uses the in-memory key. Close the session, the key is gone from memory.

macOS Keychain handles this automatically. On Linux, keychain (the tool, not the Apple thing) gives you persistent agent management across terminal sessions:

Terminal window
sudo apt install keychain
# add to ~/.bash_profile or ~/.zshrc
eval "$(keychain --eval --quiet id_ed25519)"

~/.ssh/config: Stop Typing Flags

The config file sets per-host defaults so you’re not typing -i ~/.ssh/special_key -p 2222 -J bastion.example.com every single time.

~/.ssh/config
# Default settings for all hosts
Host *
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
# Jump through a bastion host
Host internal-server
HostName 10.10.0.50
User deploy
ProxyJump bastion.example.com
# Non-standard port, different key
Host old-server
HostName legacy.example.com
Port 2222
IdentityFile ~/.ssh/id_ed25519_legacy
User ubuntu
# Port forward shortcut
Host db-tunnel
HostName db.internal.example.com
LocalForward 5432 localhost:5432
User admin

Permissions matter here, and SSH will silently fail or warn loudly if they’re wrong:

Terminal window
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

Deploying Your Public Key

The easy way:

Terminal window
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host

This appends your public key to ~/.ssh/authorized_keys on the remote host and sets permissions correctly for you.

If ssh-copy-id isn’t available, the manual method works the same way:

Terminal window
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

The permissions aren’t optional. sshd refuses to use authorized_keys if it’s world-writable. Remote directory: 700. Key file: 600. This trips people up constantly, and the error messages don’t exactly point you at the fix.


FIDO2 / Hardware Keys: The Next Level

If you want the private key to never exist on disk at all, hardware security keys are the answer. YubiKey, SoloKey, and others support FIDO2/U2F, and OpenSSH has supported them natively since 8.2.

Terminal window
# Resident key, stored on the hardware key itself
ssh-keygen -t ed25519-sk -O resident -C "yubikey"
# Non-resident: a private key handle lives on disk, the crypto lives on hardware
ssh-keygen -t ed25519-sk -C "yubikey"

The -sk suffix means “security key.” The private key material never leaves the hardware device. Even if your machine is fully compromised, the SSH key can’t be stolen without physical access to the token.

ecdsa-sk is the ECDSA variant; prefer ed25519-sk for the same reasons you’d prefer Ed25519 in general.

You can also require a physical touch per authentication:

Terminal window
ssh-keygen -t ed25519-sk -O verify-required -C "yubikey-tap"

For homelab use this is optional. For production servers or anything client-facing, it’s worth the extra button press.


Auditing Your Existing Keys

Check what you’re already carrying around:

Terminal window
# Check one key's type and size
ssh-keygen -l -f ~/.ssh/id_rsa
# Check every key in your .ssh directory
for key in ~/.ssh/id_*; do
[[ "$key" == *.pub ]] && continue
echo -n "$key: "
ssh-keygen -l -f "$key" 2>/dev/null || echo "not a key file"
done

Output from ssh-keygen -l looks like this:

2048 SHA256:abc123... [email protected] (RSA)
256 SHA256:xyz789... [email protected] (ED25519)

The number at the start is the key size. (RSA) under 3072 bits is a rotation candidate. (DSA) is an immediate rotation, no exceptions.

For servers you manage, check what’s actually sitting in authorized_keys:

Terminal window
ssh-keygen -l -f ~/.ssh/authorized_keys

This lists every authorized key with its type. Any RSA-1024 entries should alarm you. RSA-2048 entries should prompt a migration plan, not panic, just a plan.


Key Type Comparison

AlgorithmKey SizeSecurityStatusVerdict
DSA-10241024-bitBrokenDeprecated everywhereRotate immediately
RSA-10241024-bitBrokenDisabled in modern OpenSSHRotate immediately
RSA-20482048-bitMarginalNIST deprecation pendingRotate soon
RSA-40964096-bitOKStill acceptedAcceptable, but why bother
ECDSA-256256-bitGoodSupportedNIST curve concerns
Ed25519256-bitExcellentOpenSSH defaultUse this
ed25519-sk256-bitExcellent, plus physical presenceHardware requiredUse for high-value access

The One-Time Cleanup

Most people have one SSH key they generated years ago and have been carrying everywhere since. It’s probably RSA-2048, probably has no passphrase “because it’s inconvenient,” and has accumulated access to a dozen servers, cloud accounts, and GitHub over the years.

The rotation process: generate a fresh Ed25519 key with a passphrase, set up ssh-agent so you only type that passphrase once per session, deploy the new key everywhere, verify it works, then remove the old key from every authorized_keys file it’s sitting in. Don’t skip the verify step; you don’t want to find out your new key doesn’t work at the same moment you delete the old one.

This takes an afternoon and you won’t have to think about it again for years. Your 2 AM self, locked out of a server because someone finally deprecated your old RSA key on the far end, will wish you’d done this sooner.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
How to install NextCloud via Docker
Next Post
Install a php script in PHP-FPM & Caddy via Docker

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts