Post

YubiKey and FIDO2 with SSH

YubiKey and FIDO2 with SSH

OpenSSH has supported FIDO2 security keys natively since version 8.2 (February 2020). A YubiKey can hold the SSH credential itself, so the private key never lives on disk and every login needs the physical key. This post covers setting up a YubiKey for SSH, the trade-offs between key types, and what has changed in recent OpenSSH and YubiKey firmware releases.

Updated August 2026 for OpenSSH 10.5 and YubiKey firmware 5.7. Version requirements, ykman syntax and server-side options have been corrected, and a troubleshooting section added.

Requirements

ComponentRequirementNotes
OpenSSH client and server8.2 or newerNon-resident keys (ecdsa-sk, ed25519-sk)
OpenSSH client8.3 or newerResident keys (-O resident, ssh-add -K, ssh-keygen -K)
OpenSSH client and server8.4 or newer-O verify-required (PIN enforcement)
YubiKey firmware5.2.3 or newerRequired for ed25519-sk and for resident keys. Older keys must use ecdsa-sk and non-resident keys
ykman (YubiKey Manager CLI)5.xFor PIN and credential management

Check your versions before starting:

1
2
ssh -V
ykman info

Every current Linux distribution ships an OpenSSH that meets these requirements (Debian 12 has 9.2, Ubuntu 22.04 has 8.9, Ubuntu 24.04 has 9.6). On macOS the bundled OpenSSH is built without FIDO support, so install it with Homebrew (brew install openssh). On Windows the built-in client supports FIDO from 8.9 onwards; run ssh -V in PowerShell to confirm, or install the latest Win32-OpenSSH release.

Initial Setup

To use FIDO2 on a YubiKey you need to set a FIDO2 PIN first. The ykman command line tool does this. The old YubiKey Manager GUI has been replaced by Yubico Authenticator, but the CLI is still maintained and is what is used throughout this post.

1
2
3
4
5
6
7
8
# Install the YubiKey Manager CLI
sudo apt install yubikey-manager -y

# Check the FIDO2 application status, including whether a PIN is set
ykman fido info

# Set the PIN (also used to change it later)
ykman fido access change-pin

Changing the PIN does not affect your credentials. Resetting the FIDO2 application (ykman fido reset) wipes every FIDO2 and U2F credential on the key and removes the PIN. You would need to re-register the key with every service.

The FIDO2 application locks after 8 consecutive wrong PIN entries, after which the only way forward is a full reset. After 3 consecutive wrong entries the YubiKey must be unplugged and reinserted before it will accept another attempt, which is a useful safeguard against typing it wrong repeatedly.

PIN rules depend on firmware:

  • Firmware before 5.7: minimum 4 characters
  • Firmware 5.7 and newer: minimum 4 characters, or 6 when PIN complexity is enabled (which also rejects repeated and sequential characters)
  • FIPS series: minimum 6 characters, 8 on firmware 5.7.4 and newer

The PIN can be alphanumeric, so treat it as a short password rather than a numeric code.

Key Types

With SSH and FIDO2 there are two kinds of key you can generate.

  1. Non-resident key (non-discoverable). The YubiKey never stores the key. ssh-keygen writes a small key handle to ~/.ssh and the YubiKey re-derives the private key from that handle whenever it is used. Both the handle file and the YubiKey are needed to authenticate. Requires OpenSSH 8.2 or newer. Works on any FIDO2 YubiKey and does not use up credential slots.
  2. Resident key (discoverable). The credential is stored on the YubiKey itself, so it can be recovered onto any machine with ssh-keygen -K or loaded straight into an agent with ssh-add -K. Anyone with the YubiKey and the PIN can retrieve it. Requires OpenSSH 8.3 or newer and firmware 5.2.3 or newer.

Non-resident keys are the more secure default because losing the YubiKey alone does not expose the credential. Resident keys are more convenient if you move between machines. Whichever you choose, Yubico recommends adding -O verify-required so the PIN is always needed.

A YubiKey with firmware 5.7 or newer stores up to 100 resident credentials. Older 5 series keys store 25. Non-resident keys are unlimited.

Choosing the algorithm

Use ed25519-sk unless your YubiKey is older than firmware 5.2.3, in which case use ecdsa-sk. All the examples below use ed25519-sk; swap the type if needed.

Touch and PIN options

The -O flags control which factors are required each time the key is used. They apply to both resident and non-resident keys.

Factorsssh-keygen optionsDescription
Touch only (default)(none)Touch the YubiKey to authenticate. No PIN
PIN and touch (recommended)-O verify-requiredBoth the PIN and a touch are required
PIN only-O verify-required -O no-touch-requiredPIN required, no touch. Needs a server-side option, see below
Neither-O no-touch-requiredNothing beyond possession of the key. Needs a server-side option, see below

no-touch-required is refused by sshd unless the matching authorized_keys entry is prefixed with the no-touch-required option. Without it you will still be asked to touch the key. This is deliberate: the server, not the client, decides whether user presence can be skipped.

Generating a Non-Resident Key

1
2
3
ssh-keygen -t ed25519-sk -O verify-required \
    -C "laptop yubikey 10532500" \
    -f ~/.ssh/id_ed25519_sk

Touch the key when it flashes. You get ~/.ssh/id_ed25519_sk (the key handle, useless without the YubiKey) and ~/.ssh/id_ed25519_sk.pub. Use them exactly like a normal key pair. Keep the handle file backed up with your dotfiles; if it is lost the key cannot be recovered.

Generating a Resident Key

The application option tags the credential so you can store more than one SSH key on the same YubiKey. The value must start with ssh:. Without it every SSH credential uses the default ssh: application and generating a second one overwrites the first.

1
2
3
4
5
6
7
8
9
10
11
12
# Template
ssh-keygen -t ed25519-sk -O resident -O verify-required \
    -O application=ssh:your-text-here \
    -C "comment or identifier"

# Example
ssh-keygen -t ed25519-sk -O resident -O verify-required \
    -O application=ssh:servers \
    -C "Emergency Key 1 - 10532500"

# Verify the credential was stored on the YubiKey
ykman fido credentials list

ykman fido credentials list asks for the PIN and prints one line per credential showing the application (RP ID), the user name (openssh) and the credential ID. Add --csv for the full details.

Using a resident key on another machine

Either write the key handles out as files:

1
2
cd ~/.ssh
ssh-keygen -K

This creates id_ed25519_sk_rk_<application> and .pub files for every resident SSH credential on the YubiKey. Since OpenSSH 10.3 the comment is set to the application string, so the files are easy to tell apart.

Or load them directly into a running agent without writing anything to disk:

1
2
3
eval "$(ssh-agent -s)"
ssh-add -K
ssh-add -L

Both commands ask for the PIN because enumerating resident credentials always requires it.

PIN Prompts and the Agent

When you use a key file directly with ssh, the PIN prompt appears in the terminal and nothing extra is needed. When the key lives in ssh-agent, the agent has no terminal, so it needs an askpass helper to collect the PIN.

1
2
3
4
5
6
7
8
9
# Install an askpass helper (any of these work)
sudo apt install ssh-askpass -y      # X11
sudo apt install ssh-askpass-gnome -y

# Point the agent at it. Only needed if it is not picked up automatically
export SSH_ASKPASS=/usr/bin/ssh-askpass

# OpenSSH 8.4+: force the prompt into the terminal instead of a GUI window
export SSH_ASKPASS_REQUIRE=force

A helper for your ~/.bashrc:

1
2
export SSH_ASKPASS=/usr/bin/ssh-askpass
alias yubikey_ssh='eval "$(ssh-agent -s)" && ssh-add -K && ssh-add -L'

When connecting you will be prompted for the PIN, then the command line will appear to hang. Touch the YubiKey to complete the login.

If you use ssh -A (agent forwarding) the remote host can ask your YubiKey to sign, but you still have to touch it locally for each use. Avoid forwarding unless you need it. ssh -J (ProxyJump) does not require forwarding.

Server-Side Enforcement

The verify-required and no-touch-required flags baked into a key are honoured by the client, but the server can also insist on them. Per key in authorized_keys:

1
2
verify-required sk-ssh-ed25519@openssh.com AAAA... Emergency Key 1
no-touch-required sk-ssh-ed25519@openssh.com AAAA... build-bot

Or globally in sshd_config (OpenSSH 8.4+):

1
PubkeyAuthOptions verify-required touch-required

With PubkeyAuthOptions set, a user cannot bypass the policy by generating a key without -O verify-required. touch-required there simply rejects keys created with no-touch-required.

Managing Credentials

1
2
3
4
5
6
7
8
9
10
# List resident credentials on the YubiKey
ykman fido credentials list

# Delete one. The argument is any unique substring of the credential ID
# shown in the list output, so use the ID rather than the "ssh:" name
# when you have more than one SSH credential on the key
ykman fido credentials delete a1b2c3d4

# Wipe the whole FIDO2 application (all credentials, all services, PIN)
ykman fido reset

Since OpenSSH 10.5, ssh-keygen -p -O verify-required -f ~/.ssh/id_ed25519_sk can add or remove the verify-required and touch-required flags on an existing FIDO key file when resetting its passphrase, so you no longer need to regenerate a key to tighten it.

Best Practices

  • Have a backup YubiKey. A FIDO key cannot be copied. Generate a separate key on a second YubiKey and add both public keys to your servers. Note down which is which in the -C comment.
  • Use -O verify-required. Touch alone proves the key is present; the PIN proves it is you. This is also Yubico’s recommendation.
  • Prefer non-resident keys unless you actually move between machines. Losing the YubiKey then reveals nothing.
  • Use -O application=ssh:... for every resident key so you never overwrite one by accident.
  • Enforce on the server with PubkeyAuthOptions verify-required so client mistakes do not weaken the policy.
  • Do not put the FIDO PIN on the same lockout budget as everything else. Eight wrong attempts wipes the FIDO2 application, which also covers passkeys registered with other services on that key.
  • GitHub, GitLab and others accept sk-ssh-ed25519@openssh.com public keys. Paste the .pub file as usual and test with ssh -T git@github.com.

Troubleshooting

SymptomCause / Fix
Key enrollment failed: requested feature not supportedFirmware older than 5.2.3. Use ecdsa-sk, or drop -O resident
Key enrollment failed: invalid format on ed25519-skSame as above, the key cannot do Ed25519 in FIDO2
unknown key type ed25519-skOpenSSH built without FIDO support (macOS bundled ssh). Install via Homebrew
Confirm user presence hangsTouch the key. If it never flashes, check libfido2 can see it: fido2-token -L
sign_and_send_pubkey: signing failed ... agent refused operationThe agent could not get the PIN. Set SSH_ASKPASS or SSH_ASKPASS_REQUIRE=force
Permission denied with no-touch-required keyAdd the no-touch-required option to the authorized_keys line on the server
ykman cannot open the device on LinuxMissing udev rules. Install libfido2-1 / libu2f-udev and replug the key

References

This post is licensed under CC BY 4.0 by the author.