Home Blog

PGP and SSH keys on a YubiKey

Two separate things that both end up on the same piece of hardware: a PGP keypair whose private half lives on the card, and FIDO2-backed SSH keys. They solve different problems and are worth keeping straight.

Generating and moving a PGP key

gpg --expert --full-gen-key

Export everything before it goes anywhere near the card. keytocard is a move, not a copy: it writes the key to the card and removes the private half from your keyring. Without a backup at this point the key is gone the moment the YubiKey is lost.

gpg --export-secret-key --armor $KEYID > privkey.armor
gpg --export-secret-key $KEYID | paperkey > privkey.paperkey
gpg --export $KEYID > pubkey.armor

paperkey strips out everything reconstructible from the public key and leaves only the secret bits, which is what makes a printed copy a reasonable size. Store the armored copy somewhere offline.

Then move it:

gpg --edit-key $KEYID

and keytocard. Select the key first by entering key 1, otherwise the command operates on the primary key rather than the subkey you meant.

Restoring on another machine

The card holds the private key, but a fresh machine still has no idea the public key exists. Fetch it from a keyserver:

gpg --keyserver hkps://keys.openpgp.org --search-keys "[email protected]"

Then pull the card's public key stubs, which tell GnuPG that the private key lives on hardware:

gpg --edit-card

and fetch.

Finally, trust it:

gpg --edit-key $KEYID

and trust. Note that this is --edit-key, not --edit-card. Getting these two confused is easy and the error messages do not point at the mistake.

git credentials through pass

pass-git-helper lets git pull credentials out of a pass store, which is itself encrypted to the PGP key on the card:

yay -S pass-git-helper
git config --global credential.helper /usr/bin/pass-git-helper
git config credential.useHttpPath true

Create ~/.config/pass-git-helper/git-pass-mapping.ini:

[github.com]
target=github.com/<username>

credential.useHttpPath true matters if you keep per-repository credentials, since without it every host collapses to a single entry.

FIDO2-backed SSH keys

Separate mechanism, same token. OpenSSH 8.2 added the ed25519-sk and ecdsa-sk key types, where the private key is bound to the hardware token and useless without it. libfido2 is required.

Both ends need to understand the key type, so an old server will reject these outright.

ssh-keygen -t ed25519-sk

You will be asked for the PIN and a touch to confirm generation, and normally a touch on every connection afterwards.

Point your config at the key explicitly:

Host SERVER1
    IdentitiesOnly yes
    IdentityFile ~/.ssh/id_ed25519_sk

IdentitiesOnly yes is worth setting. Without it, ssh offers every key it can find, which on a token means a touch prompt for keys you did not intend to use.

Skipping the touch requirement

ssh-keygen -O no-touch-required -t ed25519-sk

Three caveats, in increasing order of annoyance:

An ecdsa-sk keypair works the same way, with the usual reasons to prefer ed25519 over ECDSA still applying.

Reference: the Arch wiki's SSH keys page and Yubico's importing keys guide.