An OpenPGP authentication subkey for SSH
These steps use gpg and ssh, which exist today. Making the subkey with ritueel, so that an offline master key binds a subkey born on the card, is planned (issue #18). Try everything on a test key first. To learn how the master key and the subkeys fit, read the OpenPGP custody chain.
An OpenPGP key can log you in to SSH servers. The key is a subkey with the authentication flag (A), kept on a card, so it cannot be copied off your computer and each use needs the PIN and, if the card asks, a touch. The master key stays offline.
What you start with
Section titled “What you start with”- A card with an authentication subkey born on it, bound by your master key. A signing and an encryption subkey may be on the same card.
gpg2.2 or later withscdaemon, and ansshclient.- The card’s PIN in your password manager.
Way 1: gpg-agent as the SSH agent
Section titled “Way 1: gpg-agent as the SSH agent”This is the usual way. gpg-agent speaks the SSH agent protocol and uses the card through scdaemon.
- Turn on SSH support. Add
enable-ssh-supportto~/.gnupg/gpg-agent.conf, then restart the agent:gpgconf --kill gpg-agent. - Point SSH at the agent. In your shell profile, set
SSH_AUTH_SOCKto the output ofgpgconf --list-dirs agent-ssh-socket. - List the key SSH sees.
ssh-add -Lshould print one line for the authentication subkey. If it does not, plug the card in and rungpg --card-statusonce sogpglearns the key. - Export the public half.
gpg --export-ssh-key <fingerprint>!prints the line to put inauthorized_keys, or to hand to the CA that signs SSH certificates (SSH). The!selects that exact subkey. - Log in.
ssh host. The agent asks for the PIN, and touches follow your card’s policy.
What gpg-agent offers over SSH (read from the GnuPG manual and source, 2026-11-01): it uses the authentication (A) key; it speaks Ed25519, RSA, ECDSA (P-256, P-384, P-521) and certificates of those types; it does not speak the FIDO sk-* key types. Keys on an active OpenPGP card are used without listing them; the sshcontrol file that lists keygrips by hand is deprecated in favour of a Use-for-ssh attribute on the key.
Way 2: the card through PKCS#11
Section titled “Way 2: the card through PKCS#11”An SSH client can also load a PKCS#11 module that talks to the card directly, with PKCS11Provider in ~/.ssh/config or ssh -I. Which module you need depends on the card: OpenSC’s module reads OpenPGP cards, and a YubiKey’s PIV applet has its own module. The key then comes from whatever the module exposes, not from gpg.
For a YubiKey’s PIV applet, the usual module is libykcs11: ssh -I libykcs11.so user@host for one login, ssh-add -s libykcs11.so to put the card’s keys in the agent, and ssh-keygen -D libykcs11.so -e to export the public keys. PIV slot 9a asks for the PIN once for a session and slot 9c for every signature.
Choose this when you do not want gpg-agent in the loop. Know the cost: a module and scdaemon both want the card, and a program that holds it keeps the others out. Do not run both at once, and keep the root cards, which are used only at ceremonies, away from your daily card.
A third way: a FIDO2 key
Section titled “A third way: a FIDO2 key”OpenSSH can also make a key on a FIDO2 token (ssh-keygen -t ed25519-sk), with options for a resident key and for requiring the PIN. It does not use your OpenPGP key at all, and gpg-agent cannot serve it. Choose it when you want an SSH key that is separate from your OpenPGP identity.
How you know it worked
Section titled “How you know it worked”ssh -v hostsays it offers the key by its fingerprint and the server accepts it.- Unplug the card and the login fails. That is the point.
When something goes wrong
Section titled “When something goes wrong”- The agent offers no key. Another program holds the card. Close it, or restart
scdaemon. - A wrong PIN three times blocks the PIN. Do not retry guesses. The admin PIN unblocks it; see recover if the card is lost instead.
- The card is lost. The key on it cannot be used by anyone without the PIN. Remove its public key from every
authorized_keysand CA list, then revoke the subkey (Revoke a key).
Do all of this on a test key and a throw-away server first. End the drill by removing the key from the server and checking that the login now fails.