TechEarl

SSH Keys With a YubiKey (FIDO2 / U2F)

Generate a hardware-backed SSH key on a YubiKey with one ssh-keygen command. How FIDO2/U2F SSH keys work, the difference between ed25519-sk and ecdsa-sk, resident keys, and the firmware and OpenSSH versions you need.

Ishan Karunaratne⏱️ 10 min readUpdated
Share thisCopied
Create a hardware-backed SSH key on a YubiKey with ssh-keygen -t ed25519-sk. How FIDO2/U2F SSH keys work, ed25519-sk vs ecdsa-sk, resident keys, and the OpenSSH and firmware versions you need.

A FIDO2 SSH key on a YubiKey is one command:

bash
ssh-keygen -t ed25519-sk

The -sk suffix is the whole story: it stands for "security key". When you run that, OpenSSH talks to the YubiKey, the key's LED starts blinking, you tap it, and you end up with a keypair where the private half physically cannot leave the hardware. What lands in ~/.ssh is not the private key, it is a handle that is useless without the YubiKey plugged in. Steal the laptop, copy the file, and you still cannot authenticate. That is the entire point: the secret lives in a chip you can pull out of the USB port.

This needs OpenSSH 8.2 or newer on both ends and a FIDO2 key. I cover the version traps below, because they are where people actually get stuck.

Generate the key

Plug in the YubiKey first, then run:

bash
ssh-keygen -t ed25519-sk -C "you@example.com"

A real run looks like this:

code
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter file in which to save the key (/home/techearl/.ssh/id_ed25519_sk):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/techearl/.ssh/id_ed25519_sk
Your public key has been saved in /home/techearl/.ssh/id_ed25519_sk.pub

Between "touch your authenticator" and the saved paths, the YubiKey blinks and waits for a physical tap. No tap, no key. That tap is user presence: proof a human is at the machine, not a script.

You now have two files, but they are not what ssh-keygen normally produces. id_ed25519_sk is a key handle plus metadata, not a usable private key on its own. id_ed25519_sk.pub is the public key you install on servers, exactly like any other public key. The crucial difference from a software key: copying id_ed25519_sk to another machine gets an attacker nothing, because the actual private scalar never left the YubiKey's secure element.

Run ssh-keygen as your normal user. It writes to your own ~/.ssh, so no sudo. (Using sudo here makes the handle root-owned and unreadable to you, same mistake as with a regular ssh-keygen run.)

ed25519-sk or ecdsa-sk?

Two algorithms carry the -sk suffix. Pick ed25519-sk if your YubiKey supports it, fall back to ecdsa-sk if it does not.

bash
ssh-keygen -t ed25519-sk     # preferred
ssh-keygen -t ecdsa-sk       # fallback for older keys

The catch is firmware. ed25519-sk needs a YubiKey on firmware 5.2.3 or later. Anything older only does ecdsa-sk. Check your firmware with:

bash
ykman info

If ssh-keygen -t ed25519-sk returns Key enrollment failed: requested feature not supported, that is almost always old firmware, and ecdsa-sk will work instead. YubiKey firmware is burned in at the factory and cannot be upgraded, so an older key is stuck on ecdsa-sk for life. That is not a real security problem; it is just a less modern curve.

AlgorithmCurveYubiKey firmwareWhen to pick it
ed25519-skEd255195.2.3 or laterDefault. Modern curve, short keys, the one I reach for.
ecdsa-skNIST P-2565.1.x and laterOnly when the key's firmware is too old for ed25519-sk.

Both require OpenSSH 8.2+ on the client that generates and uses the key, and on the server you log in to (the server has to understand the sk key type in authorized_keys).

One trap that catches people on minimal Linux installs: OpenSSH talks to the YubiKey through a middleware library, libfido2, and the ssh-sk-helper binary that ships with OpenSSH. If ssh-keygen -t ed25519-sk fails with something like Couldn't get key types: error in libcrypto or a flat "provider" error even though ssh -V shows 8.2+, you are usually missing libfido2 (sudo apt install libfido2-1 on Debian/Ubuntu, libfido2 on Fedora/Arch). Distro-packaged OpenSSH normally pulls it in; hand-built or stripped-down images often do not.

Resident keys: surviving a fresh laptop

By default the handle file matters: lose id_ed25519_sk and you cannot use the key even with the YubiKey, because the handle is half of what is needed. A resident (discoverable) key changes that. It stores the handle on the YubiKey itself, so you can walk up to any machine, plug the key in, and pull the credential back down.

bash
ssh-keygen -t ed25519-sk -O resident

On a fresh machine, retrieve the resident keys straight off the hardware:

bash
ssh-keygen -K

That writes id_ed25519_sk_rk and its .pub into the current directory. The trade-off is real: resident keys consume one of the YubiKey's limited FIDO2 credential slots, and anyone who has both the unlocked YubiKey and (if set) its FIDO2 PIN can extract them. They also need firmware 5.2.3 or later; older 5.1.x keys do FIDO2 but not discoverable credentials. For most people the non-resident default is the right call. Reach for -O resident only when "I need to log in from a machine that has never seen my key handle" is a genuine requirement, for example a recovery scenario or a shared jump box.

Useful generation options

ssh-keygen exposes the FIDO2 options through -O:

OptionEffect
-O residentStore the handle on the key itself (discoverable).
-O verify-requiredRequire a PIN (or biometric) in addition to the touch.
-O no-touch-requiredDrop the touch requirement (weakens the guarantee; most servers reject it).
-O application=ssh:workTag the credential with a custom application string to keep multiple keys apart.

-O verify-required is the one I add for anything sensitive. It means a stolen-and-plugged-in YubiKey is not enough; the attacker also needs the FIDO2 PIN. The server can enforce it too, with verify-required in authorized_keys, so the requirement is checked on both ends. Note that this option needs OpenSSH 8.3 or newer (it landed one release after the base sk key types), and a YubiKey on firmware 5.2.3 or later; firmware 5.1.x does not support it.

Install the public key and log in

Identical to any other SSH key. Append id_ed25519_sk.pub to the server's authorized_keys:

bash
ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@server

Then connect. The difference shows up at login: the YubiKey blinks and SSH waits for your tap before it will authenticate.

bash
ssh user@server
# Confirm user presence for key ED25519-SK ...
# (LED blinks, tap the YubiKey)

A few things to keep straight once it works:

  • The handle file (id_ed25519_sk) still needs 600 permissions like any private key, even though it is not secret on its own. SSH checks the mode regardless.
  • This pairs well with disabling password authentication on the server: once a hardware key is your login, password auth is pure attack surface.
  • The passphrase prompt during generation encrypts the handle file, not the YubiKey secret. You can change or remove that passphrase later with ssh-keygen -p, exactly as with a software key.

When not to bother

Hardware keys are not free in friction, and there are spots where a plain Ed25519 software key is the better tool:

  • Unattended automation. CI runners, cron jobs, and deploy bots cannot tap a YubiKey. A FIDO2 key requiring user presence is the wrong choice for anything that must authenticate without a human present. Use a scoped software key (or short-lived certificates) there.
  • Servers older than OpenSSH 8.2. The server side has to understand sk key types. A box stuck on an ancient OpenSSH will reject the public key outright. Check ssh -V on the server before committing.
  • You only have one key. A single YubiKey is a single point of failure: lose it and you are locked out of everything that trusts it. Enroll a backup key on every account from day one, the same way you would with any FIDO2 setup.

For everyday interactive logins from a laptop you carry around, though, a FIDO2 SSH key is the strongest practical setup: the private key is in silicon you control, and every login needs a deliberate physical tap.

See also

Sources

Authoritative references this article was fact-checked against.

TagsSSHYubiKeyFIDO2U2Fssh-keygenEd25519Hardware Security KeySecurity

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Use Multiple SSH Keys with ~/.ssh/config

Run separate SSH keys for work, personal, and GitHub by binding each to its host in ~/.ssh/config with IdentityFile and IdentitiesOnly, so the right key is always offered.

Proper Responsive Images with ACF Image Fields

The cleanest pattern for responsive images from an ACF Image field: ID return format plus wp_get_attachment_image, which produces a complete srcset and sizes attribute from registered image sizes. Plus the manual srcset pattern when you need control.

Fix SSH "Host Key Verification Failed"

Why SSH warns that the remote host identification has changed, when it is safe to clear, and the one command that removes the stale known_hosts entry: ssh-keygen -R.