Core Concepts
-
Asymmetric Cryptography Foundation
- Security keys use public-key cryptography (private key never leaves device)
- Private key performs cryptographic operations; public key stored by the service
- Prevents key theft via network interception
-
Phishing Resistance
- Keys verify the origin (domain/service) cryptographically
- Won’t authenticate to lookalike sites
- Challenge-response tied to the legitimate service identifier
-
Hardware-Bound Secrets
- Cryptographic material stored in tamper-resistant hardware
- Extraction extremely difficult even with physical access
- Unlike passwords/OTPs that exist in transmittable form
-
User Presence Verification
- Physical interaction required (touch, button press)
- Prevents remote/background use by malware
- Proof that user authorized the operation
-
Challenge-Response Protocol
- Service sends unique challenge
- Key signs it with private key
- Service verifies signature with stored public key
- Prevents replay attacks
FIDO2
Overview
FIDO2 is an authentication standard that enables passwordless login using cryptographic keys. It consists of:
- WebAuthn (W3C standard) - browser API for authentication
- CTAP (Client-to-Authenticator Protocol) - communication between devices and authenticators (security keys, biometrics)
Use Cases
- Passwordless login to websites/apps
- Two-factor authentication (2FA)
- Phishing-resistant authentication (keys bound to specific domains)
- Works with: USB security keys, platform authenticators (Face ID, Windows Hello), mobile devices
Setup
macOS: brew install libfido2
Arch: sudo pacman -S libfido2
Setup PIN, thumprints and more using fido2-token or at chrome://settings/securityKeys
Info / Usage
Check if FIDO2 token is plugged in (Linux): fido2-token -L (assuming token on /dev/hidraw0 below)
Check who has access to token, including ACL: ls -l /dev/hidraw0 && getfacl /dev/hidraw0
Get detailed token info: fido2-token -I /dev/hidraw0
Use chrome://settings/securityKeys
sudo (and greeter) via PAM
sudo pacman -S pam-u2fcd ~/Documents/Systems/dotfiles
sudo stow -t / fido2-root
sudo ./run-scripts.sh fido2-scriptsEquivalent manual commands for sudo:
pamu2fcfg | sudo tee /etc/u2f_mappings # map username to FIDO2 public key
sudo vim /etc/pam.d/sudo # ADD AT THE TOP OF THE FILE (for pwd fallback):
auth sufficient pam_u2f.so authfile=/etc/u2f_mappingsWhat
- U2F (Universal 2nd Factor) was a standard created by Google + Yubico for hardware-based 2FA. Simple protocol: challenge → token signs it → server verifies.
- FIDO2 is the successor — a broader umbrella spec from the FIDO Alliance that includes WebAuthn (browser API) and CTAP2 (the protocol between the authenticator and the host). U2F (now called CTAP1) is a subset of FIDO2, so all FIDO2 keys are backwards-compatible with U2F.
- pam-u2f is a PAM module that speaks CTAP1/U2F (and FIDO2/CTAP2) to your token. PAM is Linux’s pluggable auth system — it lets you slot in arbitrary auth mechanisms for any service (sudo, login, ssh, etc.) without those services knowing the details.
So the chain is: sudo → PAM → pam_u2f.so → libfido2 → hidraw → token.
/etc/u2f_mappings just maps usernames to FIDO2 credential public keys, it contains no confidential info.
SSH via ED25519-SK
ed25519-sk = Ed25519 algorithm + FIDO2 protocol
Advantages
- Phishing-resistant: Cryptographic proof of server identity (unlike passwords/TOTP)
- No private key exfiltration: Malware can’t steal what’s not on disk
- Physical presence proof: Attacker needs your token + touch action
- Biometric on token: Adds second factor (PIN + fingerprint)
How to use
Note: If Apple’s built-in OpenSSH doesn’t support ed25519-sk, use homebrew’s version instead: brew install openssh.
- Generate resident ed25519 security key:
ssh-keygen -t ed25519-sk -O resident - Download resident keys from token: `cd ~/.ssh && ssh-keygen -K
These two commands create files id_ed25519_sk(_rk) and id_ed25519_sk(_rk).pub .
id_ed25519_sk is only a key handle: it is needed to use the key but is useless without the token (see “How it works” below).
Once these files are present in .ssh/ and the token in plugged in, ssh can be used as usual:
ssh -i ~/.ssh/id_ed25519_sk user@server (.ssh/id_ed25519_sk is typically a default key path so shouldn’t even be necessary)
(not tested) SSH connection multiplexing (reuse connections, avoid repeated touches). In ~/.ssh/config:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 14400
How it works
Architecture
- Private key stays on hardware token - never touches your computer
- Public key is exported and added to servers (like normal SSH)
- Challenge-response authentication: Server sends challenge → token signs it → server verifies signature
- Physical presence required: You must touch the device to authenticate
Files and Resident mode
Three items:
- The private key (not really a file - stored on the token)
- The public key
- The private key handle (specific to -sk)
- A key handle is a unique identifier/pointer that tells the token “use THIS specific key”
- Safety deposit box analogy:
- The safety deposit box (token) holds the actual private key
- The key handle is like the box number + access code
- You need both the handle AND the physical token to access the private key
- Safety deposit box analogy:
- So both the private key and its handle are needed to use SSH
- The handle must always be stored locally on the machine (OpenSSH needs to know the key exists)
The difference between resident and non-resident keys is that resident keys also store the key handle + metadata on the token, which allows re-generating the key handle from the token itself using ssh-keygen -K. It makes resident keys more resilient to file loss, simpler to manage, and portable across computers.
ssh-keygen -K creates two files:
id_ed25519_sk- Contains the key handle + public key + metadataid_ed25519_sk.pub- Public key only (for servers’ authorized_keys)
OpenPGP
What It Is
OpenPGP is a smart card standard that stores cryptographic keys on hardware tokens. Unlike FIDO2 (authentication only), OpenPGP provides three key slots for different cryptographic operations.
Note the OpenPGP smart card standard isn’t the same as the general OpenPGP standard GPG is based on.
Status / troubleshooting
gpg --card-status # View token info
gpg -k / -K # List public / secret keys
ssh-add -L # Verify SSH key loaded
gpgconf --kill gpg-agent # Restart agentHow It Works
Core principle: Private keys never leave the device
- Device performs crypto operations internally (sign/decrypt/authenticate)
- You send data to device → device returns result
- Requires PIN to unlock operations
The three key slots and usage examples:
- Signature - Sign documents, git commits
- Encryption - Decrypt files/emails (hybrid: encrypts random AES key, AES encrypts file)
- Authentication - SSH login
Algorithms used (among others): ed25519 (signing/auth), cv25519/x25519 (encryption)
Setup
Generate keys on token
gpg --card-edit
> admin
> passwd # Change default PINs (User: 123456, Admin: 12345678)
> key-attr # Set to ECC → Curve 25519 for all 3 slots
> generate # Creates keys on device
> quitMake sure to backup the revocation certificate at ~/.gnupg/openpgp-revocs.d/\*.rev and the public key (gpg --armor --export <key>), as neither can be recovered from the token. (The secret key can’t be backed up — the whole point is it cannot physically leave the device).
Daily Usage with GPG
Encryption
gpg -e -r [email protected] file.txt # Encrypt for email
gpg -d file.txt.gpg > file.txt # DecryptSigning
gpg [--local-user [email protected]] -s file.txt # Sign (with specific key)
# -b creates sig in separate file (.sig)
# --clear-sign creates a readable text file (.asc)
gpg --verify signed-fileAuthentication (SSH, not tested)
sudo pacman -S pcscd
sudo systemctl enable --now pcscd # Confirm if needed
# Configure GPG for SSH
echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
# (confirm if needed):
export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
# Export the token auth key (gpg --card-edit)
gpg --export-ssh-key [email protected] > ~/.ssh/id_gpg_token.pub
# Send key to server and use
ssh-copy-id -i ~/.ssh/id_gpg_token.pub user@server
ssh user@serverDaily use with rage
age-plugin-openpgp-card setup
cargo install --locked age-plugin-openpgp-card
# Confirm ~/.gnupg/scdaemon.conf contains disable-ccid and pcsc-shared
sudo systemctl enable --now pcscd.socket
gpgconf --kill scdaemon # Once, in case age-plugin-openpgp-card doesn't return anythingFor a new token:
# Create identity file (public key + identity stub) for the connected card (cf. [[Installs#general|General]] if not working)
age-plugin-openpgp-card | tee ~/.config/age/card-name.key
chmod 600 ~/.config/age/card-name.key
echo <the-printed-out-public-key> > ~/.config/age/card-name.pub
# Note: equivalent for creating a new, non-token but passphrase-protected key:
rage-keygen | rage -p > ~/.config/age/passphrased-disk.key Use
rage -a -r (cat ~/.config/age/token2-26990838.pub) secret.txt > secret.txt.age
rage -d -i ~/.config/age/token2-26990838.key secret.txt.age