An ordinary SSH private key is a file, and anything that can read the file (malware, a poisoned package, a backup, a stolen laptop) can copy it and use it from anywhere. FIDO security keys change that. Since OpenSSH 8.2 (released 14 February 2020), OpenSSH can generate ed25519-sk and ecdsa-sk keys whose signing secret never leaves a hardware authenticator, with user presence and optional PIN verification on each signature. This guide covers how sk keys work, resident and non-resident keys, server-side enforcement in sshd, attestation-gated certificates, agent forwarding, PIV-backed keys through PKCS#11, rollout and lost-key handling. It is for platform, infrastructure and developer-experience teams who own SSH access. For inventory, rotation and the wider key lifecycle, see the SSH Key Management Guide.
Key takeaways
- A stolen sk key file is not enough. The file holds only a key handle; the per-device secret stays in the authenticator and cannot be exported, so the attacker also needs the physical key and usually a touch.
- Enforce it at the server, not just the client. Restrict PubkeyAcceptedAlgorithms to the sk types and set PubkeyAuthOptions touch-required and verify-required, or users will keep logging in with their old file keys.
- sshd counts a security key as one factor. OpenSSH's own protocol notes say a security key is a single factor even with a PIN; use AuthenticationMethods if policy requires two.
- Resident keys trade safety for portability. Storing the key handle on the authenticator lets users load the key on any computer with ssh-add -K, but a stolen authenticator then carries everything; always set a PIN first.
- sk keys are for humans. They need a touch, so they do not fit unattended automation; give service accounts short-lived SSH certificates or workload identity instead.
How sk keys work
OpenSSH's PROTOCOL.u2f document and the ssh-keygen manual describe the design. When you generate a FIDO key, the authenticator performs a FIDO enrolment using an application string (by default "ssh:") and returns a public key, a key handle, flags and optional attestation data. The private key file on disk stores the key handle; the authenticator combines it with a per-device secret that cannot be exported to derive the real signing key at authentication time.
- Key types: [email protected] (ed25519-sk) and [email protected] (ecdsa-sk), plus certificate forms [email protected] and [email protected]. sshd also accepts [email protected] signatures in its default algorithm lists.
- Signature contents: the signed blob includes a hash of the application string, flags including "user present", and a monotonic counter. The counter can be used at scale to spot concurrent use of a key extracted from hardware.
- Middleware: OpenSSH talks to the token through a provider library set by SecurityKeyProvider or $SSH_SK_PROVIDER, with a built-in "internal" USB provider that needs libfido2. The 8.4 release notes recommend libfido2 1.5.0 or later; older versions lose resident keys, PIN-required keys and multiple attached tokens.
- Token support: ecdsa-sk needs a token that supports ECDSA P-256. The 8.2 release notes say Ed25519 support on tokens is less common and not all tokens support resident keys, so check your authenticator model before choosing ed25519-sk.
Key options: client and server
Security key behaviour is set at generation time on the client and enforced on the server. The table lists the options from the current OpenSSH manuals.
| Option | Where it is set | Effect | Recommendation |
|---|---|---|---|
| resident | ssh-keygen -O at generation | Stores the key handle on the authenticator; loadable with ssh-keygen -K or ssh-add -K | Only for users who genuinely move between machines; set a PIN first |
| verify-required | ssh-keygen -O at generation (OpenSSH 8.4 and later) | Requires user verification for each signature; PIN is currently the supported method | Use for privileged and production access |
| no-touch-required | ssh-keygen -O at generation | Key signs without user presence; sshd refuses these signatures by default | Avoid; it removes the main protection against silent use |
| application | ssh-keygen -O at generation | Overrides the "ssh:" application string; must still begin with "ssh:" | Use distinct strings for separate resident keys per domain |
| write-attestation / challenge | ssh-keygen -O at generation | Saves the attestation data (sensitive) and lets an enrolment service supply the challenge | Use in a certificate enrolment workflow that verifies hardware backing |
| PubkeyAuthOptions touch-required | sshd_config | Always requires user presence, ignoring no-touch-required overrides in authorized_keys | Set on all servers |
| PubkeyAuthOptions verify-required | sshd_config | Requires the signature to attest user verification | Set on bastions and production |
| verify-required / no-touch-required | authorized_keys option or certificate extension | Per-key or per-certificate overrides | Prefer server-wide settings; review any no-touch-required |
Settings on an existing key can be changed with ssh-keygen -p and -O touch-required, no-touch-required, verify-required or no-verify-required. Note that the touch-required and verify-required server options have no effect on non-FIDO key types, which is why the algorithm restriction below matters.
Resident and non-resident keys
- Non-resident (default): the key handle is in ~/.ssh/id_ed25519_sk. The key works only on a machine that has that file and the authenticator plugged in. Losing the laptop exposes only a handle; losing the token alone exposes nothing without the file.
- Resident: the handle lives on the authenticator. The ssh-keygen manual warns that storing both parts on the token increases the chance of an attacker using a stolen authenticator. Since OpenSSH 8.4, ssh-keygen enables the FIDO 2.1 credProtect extension for resident keys, so a PIN is needed before the key can be retrieved from the token.
- Multiple resident keys: use the user option (and, if needed, application) to separate keys for different domains; since 8.9, ssh-keygen -K appends the user ID to the downloaded filename so keys are not overwritten.
- Decision: issue non-resident keys by default on managed workstations. Allow resident keys for staff who use shared or ephemeral machines, such as jump boxes, cloud desktops or incident response laptops, and require verify-required on them.
Choosing a hardware-backed SSH key type
FIDO sk keys are not the only way to keep an SSH key in hardware. OpenSSH also uses keys held on PKCS#11 tokens, which is how PIV smart cards and the PIV function of multi-protocol keys are used for SSH: the client loads a PKCS#11 provider through PKCS11Provider in ssh_config or ssh-add -s, and the public key is read with ssh-keygen -D.
| Factor | FIDO sk keys (ecdsa-sk, ed25519-sk) | PIV or PKCS#11 token keys |
|---|---|---|
| Server requirement | sshd must accept the sk algorithms (OpenSSH 8.2 or later on the server) | Standard RSA or ECDSA key types, so older servers and network devices accept them |
| Per-signature presence | Touch by default, enforceable by the server with touch-required | Depends on token PIN and touch policy; the server cannot see or enforce it |
| User verification | verify-required, attested in the signature and enforceable by sshd | Token PIN, not visible to the server |
| Client setup | libfido2 middleware, often built in | A PKCS#11 provider library installed on each client |
| Hardware proof at enrolment | FIDO attestation saved with write-attestation | Token-specific attestation where the token offers it |
| Fits best | Modern Linux and BSD fleets, developer access, code hosting | Estates already issuing PIV cards, or servers and appliances that cannot accept sk types |
For federal users who already hold PIV Cards, PKCS#11 keeps one credential for logon and SSH. See the Smart Card and PIV/CAC Authentication Guide.
Enforcing security keys on servers
- Check versions: confirm every target sshd is OpenSSH 8.2 or later (8.4 or later for verify-required). Older appliances that cannot be upgraded need the PKCS#11 route or a bastion.
- Restrict algorithms: set PubkeyAcceptedAlgorithms to the sk key and sk certificate types only (for example [email protected], [email protected] and their -cert-v01 forms). This is what actually stops file-based keys; touch-required and verify-required do nothing for non-FIDO keys.
- Require presence and verification: set PubkeyAuthOptions touch-required verify-required on privileged hosts, which overrides any no-touch-required option an authorized_keys file might carry.
- Add a second factor if policy needs one: because sshd treats a security key as a single factor, use AuthenticationMethods (for example publickey followed by another method) where a control framework demands two factors at the server.
- Lock down authorized_keys: move AuthorizedKeysFile to a root-owned location managed by configuration management, so neither users nor malware can add a file-based key that bypasses the policy.
- Revoke centrally: maintain a key revocation list and point RevokedKeys at it. The sshd manual warns that if the file is unreadable, public key authentication is refused for all users, so monitor its distribution.
For general server settings see the OpenSSH Server Hardening Guide.
Certificates and attestation
sk keys work with OpenSSH certificates, and that is where attestation pays off. OpenSSH does not store attestation by default because the attestation certificate can identify the token's manufacturer and batch, but ssh-keygen -O write-attestation saves it at enrolment. An enrolment service can supply the challenge with -O challenge, verify the attestation against the authenticator vendor's root, and only then sign a short-lived certificate. The ssh-sk-attest-v01 format (OpenSSH 8.4 onward) includes the authenticator data needed to verify the signature; the earlier v00 format did not. Certificates can also carry verify-required or no-touch-required as extensions, and the default CASignatureAlgorithms list in sshd includes the sk types. See the SSH Protocol and Certificate Authority Guide.
Agents and forwarding
- Default web-safety filter: ssh-agent refuses to sign with a FIDO key whose application string does not start with "ssh:", or for data that does not look like an SSH authentication or ssh-keygen signature, so a forwarded agent cannot be used to authenticate to websites. Do not set no-restrict-websafe without a reason.
- Destination constraints: OpenSSH 8.9 added agent key restrictions; ssh-add -h limits a key to specific hops and destinations. Use it whenever agent forwarding is needed, and prefer ProxyJump over forwarding. Agent hijacking is ATT&CK T1563.001 SSH Hijacking.
- Confirmation: ssh-add -c asks for confirmation through ssh-askpass before each use; with sk keys the touch already provides a physical confirmation.
- Remote provider loading: by default only local clients can make ssh-agent load PKCS#11 or FIDO provider libraries; leave allow-remote-pkcs11 off, and keep the -P allowed-provider path list tight.
Where identity fits: humans and machines
sk keys solve the human credential problem. They are a poor fit for CI runners, configuration management and backup jobs because each signature needs a touch, and no-touch-required on a token plugged into a server simply recreates a bearer secret. For these non-human identities, issue short-lived SSH certificates from a CA after the workload proves its identity, and inventory remaining static keys. The PyTorch torchtriton supply chain attack showed why: the malicious package uploaded the contents of ~/.ssh from developer machines, which a file-only key cannot survive but an sk key handle can, since it is useless without the token.
Rollout, lost keys and recovery
- Pilot: start with platform engineers on one bastion. Test generation, login, agent use, git over SSH and recovery.
- Enrol two authenticators per person: register a primary and a backup key so a lost token does not mean a helpdesk-driven fallback to file keys.
- Dual-run: accept both file and sk keys for a defined window, measuring adoption from logs, then remove file-key algorithms from PubkeyAcceptedAlgorithms host group by host group.
- Lost token: revoke the public key (or certificate) through the KRL, remove it from managed authorized_keys, and confirm the backup key works. A non-resident key also needs the handle file, which limits exposure, but revoke regardless.
- Rollback: keep a break-glass account using a hardware-protected key held under dual control, so a middleware or firmware problem does not lock out operations.
Detection and measurement
sshd logs successful public key logins with the key type and fingerprint (hash set by FingerprintHash). Forward these to your SIEM and alert on non-sk key types appearing on hosts that should accept only sk keys, new keys in authorized_keys files outside configuration management (ATT&CK T1098.004 SSH Authorized Keys), and the same key used from many source addresses.
- Coverage: percentage of interactive SSH logins made with sk keys or sk certificates.
- Enforcement: percentage of servers with PubkeyAcceptedAlgorithms restricted to sk types.
- Verification: percentage of privileged hosts with PubkeyAuthOptions verify-required.
- Backup enrolment: percentage of users with two registered authenticators.
- Residual file keys: count of file-based user keys still accepted anywhere, trending to zero.
Framework mapping
| Framework | Reference | How sk keys help |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-2(1), IA-2(2) Multi-factor Authentication | Hardware key with verify-required, plus AuthenticationMethods where two server-side factors are required |
| NIST SP 800-53 Rev. 5 | IA-5(2) Public Key-based Authentication; IA-5 Authenticator Management | Non-exportable private keys, revocation through KRLs |
| NIST SP 800-53 Rev. 5 | AC-17 Remote Access | Enforced key types and presence checks for SSH access |
| NIST CSF 2.0 | PR.AA-01, PR.AA-03 | Credentials managed and users authenticated with hardware-bound keys |
| CIS Controls v8.1 | 6.5 Require MFA for Administrative Access | Touch and PIN on admin SSH sessions |
| MITRE ATT&CK | T1552.004 Private Keys; T1021.004 SSH; T1098.004 SSH Authorized Keys; T1563.001 SSH Hijacking | Copied key files become unusable; authorized_keys control and agent constraints address persistence and hijacking |
Common failure patterns
- Issuing sk keys but accepting everything: without restricting PubkeyAcceptedAlgorithms, old ed25519 and RSA file keys keep working, and attackers use those.
- Writable authorized_keys: an attacker who gets code execution simply adds their own key. The Cursor Marketplace Extension Compromise 2026 payload tried to add an attacker key and enable SSH on developer machines; root-owned, centrally managed key files and alerts on new keys are the customer-side control.
- Resident keys without a PIN: anyone holding the token can load and use the key on any computer.
- no-touch-required for convenience: removes the physical confirmation that stops silent signing by malware on the workstation.
- One token per person: the first lost key leads to an emergency file key that is never removed.
- Using sk keys for automation: a token left in a server with touch disabled is a bearer credential with extra steps.
Practitioner checklist
- Confirm OpenSSH 8.4 or later on clients and servers, and libfido2 1.5.0 or later on clients.
- Generate ed25519-sk keys where authenticators support Ed25519, otherwise ecdsa-sk, with verify-required for privileged users.
- Restrict PubkeyAcceptedAlgorithms to sk key and certificate types on hosts in scope.
- Set PubkeyAuthOptions touch-required (all hosts) and verify-required (privileged hosts).
- Make authorized_keys root-owned and centrally managed, and alert on unexpected changes.
- Enrol a backup authenticator for every user, and set a PIN before creating resident keys.
- Use attestation-gated, short-lived certificates where you run an SSH CA.
- Restrict agent forwarding with ssh-add -h, and keep the default web-safety filter on.
- Maintain a KRL with RevokedKeys and monitor its distribution.
- Move service accounts to short-lived certificates or workload identity rather than sk keys.
Standards and references
- OpenSSH: PROTOCOL.u2f, support for U2F/FIDO security keys
- OpenSSH: ssh-keygen manual (FIDO AUTHENTICATOR section)
- OpenSSH: sshd_config manual (PubkeyAuthOptions, PubkeyAcceptedAlgorithms, RevokedKeys)
- OpenSSH: ssh-agent manual
- OpenSSH: Release notes 8.2
- OpenSSH: Release notes 8.4
- NIST: IR 7966 Security of Interactive and Automated Access Management Using Secure Shell
- MITRE ATT&CK: Unsecured Credentials, Private Keys
Related NHI Mgmt Group resources: SSH Key Management Guide · SSH Protocol and Certificate Authority Guide · Hardware Security Keys Guide · SSH Key Compromise Response Playbook