GnuPG with Yubikey

Posted on 1 2026

The Yubikey 5 implements the OpenPGP card specification, giving it three dedicated slots for GPG key material: one for signing, one for encryption, and one for authentication. Once your subkeys are moved onto the card, all GPG operations use the hardware rather than key files on disk. The private key material never leaves the card. Signing a message, decrypting a file, authenticating an SSH connection, all of these require the Yubikey to be physically present.

This is a meaningful security improvement over software-only key storage. A compromised machine cannot exfiltrate a key stored on hardware. It can request operations from the card, but cannot read the key itself.

Before you start

This page assumes you have completed the GPG key generation section and have a key with three subkeys: signing [S], encryption [E], and authentication [A]. It also assumes you have a backup of your private key on encrypted offline storage. If you have not done this, do it before continuing.

The keytocard operation in GPG is destructive on disk. It moves the subkey to the card and replaces the on-disk key with a stub. Once moved, the subkey cannot be used without the Yubikey, and cannot be moved to a second Yubikey from the desktop. Your offline backup is the only way to load the key onto a backup card.

Read that paragraph again before proceeding.

Prerequisites

Install the required packages if not already in place:

sudo apt install scdaemon pcscd yubikey-manager

scdaemon handles communication between GPG and the card. pcscd is the PC/SC daemon for smartcard access. yubikey-manager provides the ykman command line tool for configuring the Yubikey.

scdaemon configuration

Create ~/.gnupg/scdaemon.conf to configure scdaemon to work reliably with the Yubikey:

#
# scdaemon configuration for Yubikey 5
#

# Use pcscd for card access rather than direct CCID
# This avoids conflicts when ykman or other tools also access the card
disable-ccid

# Path to the pcsclite library
pcsc-driver /usr/lib/x86_64-linux-gnu/libpcsclite.so.1

# Disconnect from card after each operation
# Prevents stale connections after suspend/resume
card-timeout 5

# Prefer the Yubikey when multiple readers are present
reader-port Yubico YubiKey

Verify the library path exists on your system:

dpkg -L libpcsclite1 | grep libpcsclite.so

If the path differs, update scdaemon.conf accordingly. Reload the agent after saving:

gpgconf --kill gpg-agent

The agent restarts automatically on the next GPG operation.

Verify the Yubikey is detected

Insert the Yubikey and check that GPG can see it:

gpg --card-status

A successful response shows the card details including the OpenPGP application version, serial number, and three empty key slots. If GPG cannot find the card, check that pcscd is running:

sudo systemctl start pcscd
sudo systemctl enable pcscd

Then kill and restart the scdaemon:

gpgconf --kill scdaemon
gpg --card-status

Configure the card

Before moving keys onto the card, set the PIN, Admin PIN, and card metadata. The default PINs on a new Yubikey are:

  • User PIN: 123456
  • Admin PIN: 12345678

These must be changed before use. Three incorrect User PIN entries locks the card. Three incorrect Admin PIN entries destroys the key material permanently. This is not recoverable.

Enter card edit mode:

gpg --edit-card

Enable admin commands and change the PINs:

gpg/card> admin
gpg/card> passwd

Select option 1 to change the User PIN. Use a six-digit numeric PIN or a longer alphanumeric passphrase. Store it in KeePassXC.

Select option 3 to change the Admin PIN. Use a different value from the User PIN. Store it in KeePassXC.

Set the reset code (option 4). This can be used to unblock a locked User PIN without knowing the Admin PIN. Store it in KeePassXC.

Set the cardholder name and public key URL while in admin mode:

gpg/card> name
Cardholder name (surname): Doe
Cardholder name (given name): John

gpg/card> url
URL to retrieve public key: https://keys.openpgp.org/vks/v1/by-fingerprint/<your-fingerprint>

The URL allows GPG to fetch your public key automatically when the card is inserted on a new machine. Use your actual fingerprint from gpg --fingerprint $GPGKEY.

Quit card edit mode:

gpg/card> quit

Move subkeys to the card

This is the point of no return. Make absolutely certain your offline backup exists and is readable before continuing.

Open the key for editing:

gpg --edit-key $GPGKEY

The key listing should show your primary key (sec) and three subkeys (ssb) with [S], [E], and [A] capabilities. If the primary key shows #, it is correctly offline. If it shows without #, verify you followed the offline primary key steps in the key generation section.

Move the signing subkey

Select the signing subkey. In most setups it is key 1:

gpg> key 1

An asterisk appears next to the selected key. Move it to the card:

gpg> keytocard

GPG asks which slot to use. Select the signature slot (option 1).

Deselect the key before selecting the next:

gpg> key 1

Move the encryption subkey

Select the encryption subkey:

gpg> key 2
gpg> keytocard

Select the encryption slot (option 2).

Deselect:

gpg> key 2

Move the authentication subkey

Select the authentication subkey:

gpg> key 3
gpg> keytocard

Select the authentication slot (option 3).

Save and exit

gpg> save

Verify the result

Check the key listing:

gpg --list-secret-keys $GPGKEY

Each subkey should now show ssb> rather than ssb. The > indicates the key is stored on a card rather than on disk. The primary key (sec) should show # indicating it is not available locally.

Verify the card contains the keys:

gpg --card-status

The three key slots should now show fingerprints rather than [not set].

Test a GPG operation

With the Yubikey inserted, test signing:

echo "test" | gpg --clearsign

GPG will prompt for the User PIN via the Qt pinentry dialog. Enter it and the signed output should appear. The Yubikey may also blink requiring a touch, depending on whether touch confirmation is enabled.

Test decryption by encrypting something to yourself and decrypting it:

echo "test" | gpg --encrypt --recipient $GPGKEY | gpg --decrypt

Enable touch confirmation

The Yubikey 5 supports requiring a physical touch for each cryptographic operation. This prevents a compromised machine from silently using the card without your knowledge.

Enable touch for all three operations using ykman:

ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc on
ykman openpgp keys set-touch aut on

Each command prompts for the Admin PIN. After enabling touch, every GPG signing, decryption, or authentication operation will cause the Yubikey to blink, waiting for a physical touch before proceeding. This is a meaningful additional security layer.

To check the current touch policy:

ykman openpgp info

Using the card on a different machine

Moving subkeys to the Yubikey means they travel with the card, but GPG on other machines still needs the public key and a card stub to know how to use the card.

On the new machine, fetch your public key:

gpg --fetch-key https://keys.openpgp.org/vks/v1/by-fingerprint/<your-fingerprint>

Or import it directly from the card:

gpg --edit-card
gpg/card> fetch
gpg/card> quit

Set ultimate trust:

gpg --edit-key $GPGKEY
gpg> trust
# Select 5 (ultimate)
gpg> quit

GPG on the new machine can now use the card for all operations. The private key never left the hardware.

Suspend and resume issues

A known issue with scdaemon is that after a suspend/resume cycle, it can lose communication with the Yubikey. The card-timeout 5 setting in scdaemon.conf mitigates this by disconnecting after each operation, but if you encounter errors after waking from suspend:

gpgconf --kill scdaemon

Remove and reinsert the Yubikey. Scdaemon restarts automatically on the next GPG operation.

The keytocard operation is one-way from the perspective of your desktop. The key moves to the card and the on-disk copy becomes a stub. Your offline backup is the only source from which the key can be loaded onto a second card. Treat that backup accordingly.