Hardware-Backed SSH Keys with Nitrokey 3 and YubiKey 5
Store SSH keys so the private key never leaves the hardware token. The file on your computer is just a stub (a key handle) — useless without the device plugged in and touched.
This works on both Nitrokey 3 and YubiKey 5 because both speak FIDO2.
Prerequisites
- OpenSSH 8.2+ (
ssh -V) libfido2installed- macOS:
brew install libfido2 - Debian/Ubuntu:
sudo apt install libfido2-1 - Fedora:
sudo dnf install libfido2
- macOS:
- A Nitrokey 3 or YubiKey 5 with FIDO2 enabled and a PIN set
:::note Set a FIDO2 PIN first If your token has no FIDO2 PIN yet, set one before you continue:
ykman fido access change-pin # YubiKey
nitropy fido2 set-pin # Nitrokey
:::
Generate the key
Plug in the token and run:
ssh-keygen -t ed25519-sk -O resident -O verify-required -f ~/.ssh/id_ed25519_sk
You'll be prompted to enter the FIDO2 PIN and touch the device.
What just happened:
ed25519-sk— Ed25519 key bound to a security key. Useecdsa-skonly if your device doesn't support Ed25519.resident— the stub is stored both on disk and on the token, so you can recover it on any machine with just the device. The actual private key material never leaves the token.verify-required— require PIN + touch on every use (recommended).
You'll get two files:
| File | Contents |
|---|---|
id_ed25519_sk | The stub (key handle). Useless without the hardware token. |
id_ed25519_sk.pub | The public key. Copy this to servers. |
Use it on another machine
Plug the token into a fresh machine and pull the stub back:
ssh-keygen -K
This writes id_ed25519_sk and id_ed25519_sk.pub into the current directory.
Move them to ~/.ssh/ and you're ready to connect.
Use it
Copy the public key to a server as usual:
ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@host
Then connect:
ssh user@host
OpenSSH will ask for your PIN and a touch. That's it.
Non-resident keys (alternative)
If you'd rather not store the stub on the token (e.g. to avoid leaking which identities exist on the device, or to save one of its limited slots), use:
ssh-keygen -t ed25519-sk -O verify-required -f ~/.ssh/id_ed25519_sk
Difference: the stub lives only on disk. You'll need to back it up and
copy it to every machine yourself — ssh-keygen -K won't recover it. The
private key still never leaves the token.
Troubleshooting
Key enrollment failed: invalid format— your OpenSSH is too old. Checkssh -Vand upgrade to 8.2+.provider /usr/lib/ssh/sk-helper... not found—libfido2isn't installed, or the helper isn't on$PATH.- Touch ignored / hangs — on Linux, make sure your user has access to the
HID device. Most distros ship udev rules for both vendors; otherwise install
libfido2's rules package or add a rule for the device's USB IDs. - Multiple tokens plugged in — unplug all but one during
ssh-keygen.
Summary
ssh-keygen -t ed25519-sk -O resident -O verify-required -f ~/.ssh/id_ed25519_sk
ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@host
ssh user@host # PIN + touch
:::tip Threat model The private key never touches your disk. Lose the laptop → no compromise. Lose the token → revoke the public key on your servers. :::