Table of Contents
- What is GPG?
- YubiKey GPG Keys
- Configuration
- Using YubiKey on New Systems
- Test YubiKey GPG Operations
- Troubleshooting Issues
- What’s Next?

Part 3 - Using YubiKeys with GPGs.
Note: All the below configuration is done in Fedora with GNOME DE (applicable 1-to-1 on related distros) and YubiKey 5C / USB (not the Biometric version).
What is GPG?
According to the official website:
GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC 4880. GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. A wealth of frontend applications and libraries are available. GnuPG also provides support for S/MIME and Secure Shell (ssh).
YubiKey GPG Keys
When working with GPG keys, YubiKey uses its OpenPGP application to store GPG private keys in its secure element, which prevents them from being extracted under any circumstance.
Secure Element: Tamper-resistant chip inside YubiKey that stores cryptographic material. Data stored here cannot be extracted, even with physical access to the hardware.
All cryptographic operations (like signing, decrypting, authenticating) execute within this secure element and expose only the results.
# Show Yubikey information, such as enabled applications
ykman info
# Command output:
Device type: YubiKey 5C
...
Applications
...
FIDO2 Enabled
OpenPGP Enabled # This is the application used
Key Slots
The OpenPGP YubiKey application has 3 dedicated slots, each serving a specific function.
- Signature Slot: Used for creating digital signatures
- Encryption Slot: Used for decrypting data
- Authentication Slot: Used for authentication operations (e.g., SSH)
Each slot can hold one RSA, ECC, or Ed25519/Cv25519 key pair.
Adding GPG Keys to YubiKey
The YubiKey OpenPGP application supports 2 ways for provisioning GPG keys:
On-Device GPG Key Generation
- Keys are generated in the secure element and never exist outside the YubiKey.
- Because of this, they are very secure but cannot be backed up. If the YubiKey is lost or damaged, the keys are permanently lost.
Generate GPG Key Externally and Then Import To YubiKey
- Keys are generated on your computer and then imported to the YubiKey.
- This allows you to backup the key, import the same key to multiple YubiKeys, and maintain a master key for subkey rotation (more on that later).
- Recommended approach for most users.
- Note: Once imported to the YubiKey, private keys cannot be extracted or exported from the secure element. The device will perform all cryptographic operations internally, exposing only the results.
Why Use GPG with YubiKey?
Why not? As many things, YubiKey improves security at many levels.
Even if your computer is compromised, private keys will remain inaccessible to the attacker. Signing/decryption operations require access to the YubiKey.
The OpenPGP Key Topology
OpenPGP uses a hierarchical trust model where a single Master Key can create specialized Subkeys.
This architecture allows SubKeys to be used, stored in the YubiKey, and rotated without losing your cryptographic identity (stored in the Master Key, which should be kept in an offline secured location).
| GPG Key Type | Capability | Storage Location | Description |
|---|---|---|---|
| Master Key | Certify | Secure Offline Storage | This is your identity. Used to: create and revoke subkeys, sign other people’s keys (web of trust), update key expiration dates. Never store it in the YubiKey. Keep it offline on a secure location, so that if your YubiKey is stolen or damaged, you can use the Master Key to revoke the old subkeys from the YubiKey, generate new subkeys and sign them, maintaining your identity and web of trust. |
| Signing Subkey | Sign | YubiKey Slot | Used to sign data (e.g., Git commits, emails). Requires YubiKey touch and PIN for every signing operation. |
| Encryption Subkey | Encrypt | YubiKey Slot | Used to decrypt files and messages that were encrypted by your public key. Requires YubiKey touch and PIN for every decryption operation. |
| Authentication Subkey | Authenticate | YubiKey Slot | Used for SSH authentication via the GPG Agent and PAM authentication. Some people prefer this over FIDO2 for managing SSH access. |
Configuration
Install Required Packages
What Does Each Each Package Do?
gnupg2: GPG implementation.pinentry-gnome3: GNOME-integrated PIN entry dialog.pcsc-lite: PC/SC smart card middleware.pcsc-lite-ccid: Generic USB CCID smart card reader driver.yubikey-manager: Official Yubico CLI tool (ykman) to manage the YubiKey.
# Install GPG and smart card tools
sudo dnf install gnupg2 pinentry-gnome3 pcsc-lite pcsc-lite-ccid
# Install YubiKey management tools (if not already installed)
sudo dnf install yubikey-manager
# Enable and start the PC/SC Smart Card daemon
sudo systemctl enable --now pcscd
Generate GPG Keys (External Generation)
Best practice is to generate GPG keys externally (not on the YubiKey) to allow for backups and then move the Subkeys to the YubiKey.
If you already have your GPG keys generated, skip this section.
Generate Master Keys
- My recommendation is to generate Ed25519/Cv25519 keys.
# Generate master key and subkeys
gpg --expert --full-generate-key
# Pretty much all default options are the most secure ones
Add Subkeys
# Ger your GPG master key ID with gpg --list-keys
# or just put the email specified when generating the master key
# Edit the master key to add subkeys
# Syntax: gpg --expert --edit-key KEY_ID_OR_EMAIL
gpg --expert --edit-key [email protected]
# At the gpg> prompt, add 3 subkeys:
# 1. Signing subkey [S]
gpg> addkey
# Choose: (10) ECC (sign only)
# Choose: (1) Curve 25519 *default*
# Expiration: 1y
# 2. Encryption subkey [E]
gpg> addkey
# Choose: (12) ECC (encrypt only)
# Chosse: (1) Curve 25519 *default*
# Expiration: 1y
# 3. Authentication subkey [A]
gpg> addkey
# Choose: (11) ECC (set your own capabilities)
# Choose: S (toggle off sign capability)
# Choose: A (toggle on authenticate capability)
# Choose: Q (finish)
# Chosse: (1) Curve 25519 *default*
# Expiration: 1y
# Save and exit - if not saved, changes revert
gpg> save
List Generated Keys
Show the generated GPG master and subkeys. When listing these keys, you’ll see single-letter capability flags, which makes them easy to recognize their function:
[C]- Certification (Master Key).[S]- Signing.[E]- Encryption.[A]- Authentication.
# Show GPG keys generated (master and subkeys)
gpg --list-secret-keys --keyid-format LONG [email protected]
# Command output
# sec ed25519/XXXXXXXX 2025-11-15 [C]
# created: 2025-11-15 expires: never usage: C
# trust: ultimate validity: ultimate
# ssb ed25519/XXXXXXXX 2025-11-15 [S] [expires: 2026-11-15]
# ssb cv25519/XXXXXXXX 2025-11-15 [E] [expires: 2026-11-15]
# ssb ed25519/XXXXXXXX 2025-11-15 [A] [expires: 2026-11-15]
Backup Your GPG Keys (IMPORTANT)
Remember that once the subkeys are added to the YubiKey, they cannot be extracted from the YubiKey AND private subkeys are also removed from the local machine keyring, so always backup before transferring.
# Export private keys (do this BEFORE moving to YubiKey)
gpg --export-secret-keys --armor KEY_ID_OR_EMAIL > gpg_master_key.asc
gpg --export-secret-subkeys --armor KEY_ID_OR_EMAIL > gpg_subkeys.asc
# Export public key
gpg --export --armor KEY_ID_OR_EMAIL > gpg_public_key.asc
# Export trust database
gpg --export-ownertrust > gpg_trust.txt
# Store these files on a secure encrypted backup (preferrably offline)
Working With YubiKey
Check YubiKey OpenPGP Status
Before making any changes, verify the current state of your YubiKey’s OpenPGP application:
# View OpenPGP application info
ykman openpgp info
# Command output:
# OpenPGP version: 3.4
# Application version: 5.7.2
# PIN tries remaining: 3
# Reset code tries: 0
# Admin PIN tries: 3
# Require PIN for signature: Once
# View detailed card status using GPG
gpg --card-status
# Command output:
# Reader ...........: Yubico YubiKey OTP FIDO CCID 00 00
# Application ID ...: XXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Application type .: OpenPGP
# Version ..........: 3.4
# Manufacturer .....: Yubico
# Serial number ....: 12345678
# Name of cardholder: [not set]
# ...
Note: If the YubiKey smart card cannot be read, check the Troubleshooting Issues section at the end.
Change Default PINs (IMPORTANT)
The YubiKey ships with factory default PINs that must be changed.
- OpenPGP Application PINs: These are different from the FIDO2 application PINs.
- User PIN lockout: 3 failed attempts locks the User PIN (can only be unlocked with Admin PIN)
- Admin PIN lockout: 3 failed attempts permanently locks the OpenPGP application (requires factory reset)
| PIN Type | Purpose | Default Value | Security Considerations |
|---|---|---|---|
| User PIN | Daily operations (sign, decrypt, authenticate) | 123456 | You’ll type this frequently so balance security with usability. |
| Admin PIN | Admin tasks (key import, settings changes) | 12345678 | Should be complex and stored in a secure location. |
# Change User PIN
ykman openpgp access change-pin
# Enter PIN: (if first time, type the default value: 123456)
# New PIN:
# Repeat for confirmation:
# Change Admin PIN
ykman openpgp access change-admin-pin
# Enter PIN: (if first time, type the default value: 12345678)
# New PIN:
# Repeat for confirmation:
Move Subkeys to YubiKey
The keytocard operation permanently moves your private GPG subkey to the YubiKey and removes it from your GPG keyring. The master key remains on your computer for future subkey management.
Before Moving your Subkeys to YubiKey Make Sure That:
- Master and Subkeys are backed up.
- Default PINs are changed to strong values.
# Edit the key
gpg --edit-key KEY_ID_OR_EMAIL
# List keys to see subkey numbers
gpg> list
# Move signing subkey
gpg> key 1 # Select 1st subkey (signing) - selection shows ssb*
gpg> keytocard
# Choose slot: 1 (Signature key)
# Enter passphrase and Admin PIN when prompted
gpg> key 1 # Deselect key 1
gpg> key 2 # Select 2nd subkey (encryption) - selection shows ssb*
gpg> keytocard
# Choose slot: 2 (Encryption key)
# Enter passphrase and Admin PIN when prompted
gpg> key 2 # Deselect key 2
gpg> key 3 # Select 3rd subkey (authentication) - selection shows ssb*
gpg> keytocard
# Choose slot: 3 (Authentication key)
# Enter passphrase and Admin PIN when prompted
gpg> key 3 # Deselect key 3
# Save and exit
gpg> save
Verify Keys Are on YubiKey
Check Card Status
- Output should show the GPG subkeys.
# Check card status
gpg --card-status
# You should see your 3 subkeys listed:
# Signature key ....: [fingerprint]
# Encryption key....: [fingerprint]
# Authentication key: [fingerprint]
# sec ed25519/XXXXXXXX 2025-11-15 [C]
# created: 2025-11-15 expires: never usage: C
# trust: ultimate validity: ultimate
# ssb> ed25519/XXXXXXXX 2025-11-15 [S] [expires: 2026-11-15]
# ssb> cv25519/XXXXXXXX 2025-11-15 [E] [expires: 2026-11-15]
# ssb> ed25519/XXXXXXXX 2025-11-15 [A] [expires: 2026-11-15]
List Secret Keys
- In the output, notice the
>symbol afterssb- this confirms the subkeys are now stored on the YubiKey, not in your GPG keyring.
# List secret keys (should show "ssb>" indicating keys are on card)
gpg --list-secret-keys
# Command output
# sec ed25519/XXXXXXXX 2025-11-15 [C]
# created: 2025-11-15 expires: never usage: C
# trust: ultimate validity: ultimate
# ssb> ed25519/XXXXXXXX 2025-11-15 [S] [expires: 2026-11-15]
# ssb> cv25519/XXXXXXXX 2025-11-15 [E] [expires: 2026-11-15]
# ssb> ed25519/XXXXXXXX 2025-11-15 [A] [expires: 2026-11-15]
Configure Touch Requirement (IMPORTANT)
Requiring a physical touch for each GPG operation prevents malware from using your keys without explicit physical contact (even if it captures your PIN).
# Require touch for all operations
ykman openpgp keys set-touch sig on # Signing
ykman openpgp keys set-touch enc on # Encryption
ykman openpgp keys set-touch aut on # Authentication
# Options:
# on - Touch required, but cached for 15 seconds
# off - No touch required
# fixed - Touch required every time, no caching (most secure)
# cached - Touch required, cached until YubiKey is removed
PIN Management (OPTIONAL)
- Set retry limits.
ykman openpgp access set-retries 5 5 5
Remove Master Key From Local Keyring (OPTIONAL)
Export Private Master Key
- Export the private master key and back it up in a secure offline location.
- For maximum security, this master key should be kept offline, not on your computer. You will be working with the subkeys.
gpg --export-secret-keys -a [email protected] > gpg_master_key.asc
# Then save it to an encrypted USB drive or something secure
Remove Master Key from Computer
# Delete master secret key from GPG keyring
# Ensure you have offline backups before doing this!!
gpg --delete-secret-keys [email protected]
# Re-import only your public keys and card stubs (from previous sections)
gpg --import gpg_public_key.asc
Verify Master Key Is Removed
- The
#symbol next to the private key means the master key is not available.
# Verify: master key should show 'sec#'
gpg --list-secret-keys
# Command output
# sec# ed25519/XXXXXXXX 2025-11-15 [C] # The master key is offline
# created: 2025-11-15 expires: never usage: C
# trust: ultimate validity: ultimate
# ssb> ed25519/XXXXXXXX 2025-11-15 [S] [expires: 2026-11-15]
# ssb> cv25519/XXXXXXXX 2025-11-15 [E] [expires: 2026-11-15]
# ssb> ed25519/XXXXXXXX 2025-11-15 [A] [expires: 2026-11-15]
Configure GPG Agent
Configure the GPG agent to manage PIN caching, SSH support, and PIN entry dialogs.
Edit ~/.gnupg/gpg-agent.conf
# Edit ~/.gnupg/gpg-agent.conf
vim ~/.gnupg/gpg-agent.conf
# ===================================================================
# PIN Entry Program
# ===================================================================
pinentry-program /usr/bin/pinentry-gnome3
# Alternatives:
# pinentry-program /usr/bin/pinentry # Original vlue
# pinentry-program /usr/bin/pinentry-curses # Terminal-only
# pinentry-program /usr/bin/pinentry-qt # KDE/Qt environments
# ===================================================================
# SSH Support
# ===================================================================
enable-ssh-support
# ===================================================================
# GPG Key Passphrase Caching (disk-based keys)
# ===================================================================
default-cache-ttl 3600 # 1 hour idle timeout
max-cache-ttl 7200 # 2 hour absolute maximum
# ===================================================================
# SSH Key Passphrase Caching
# ===================================================================
default-cache-ttl-ssh 3600 # 1 hour idle timeout
max-cache-ttl-ssh 7200 # 2 hour absolute maximum
# ===================================================================
# Security Settings
# ===================================================================
no-allow-external-cache
no-allow-mark-trusted
# ===================================================================
# Display Settings
# ===================================================================
keep-display
keep-tty
Set Secure Permissions
chmod 600 ~/.gnupg/gpg-agent.conf
Restart the GPG Agent To Implement Changes
# Kill existing agent
gpgconf --kill gpg-agent
# Start new agent (happens automatically on next GPG operation)
gpg-agent --daemon
# Verify agent is running
gpgconf --list-dirs agent-socket
Enable SSH Support (Optional)
If you plan to use the Authentication subkey for SSH login, point the SSH_AUTH_SOCK environment variable to point to the GPG agent.
Create Variable Definition
- Add the variable export to as script in ~/.bashrc.d/ to make it persistent.
- Make sure your ~/.bashrc file reads this drop-in directory - which most distros do.
# Ensure regular user's .bashrc sources ~/.bashrc.d (optional)
grep -q "bashrc.d" ~/.bashrc || echo '
# Source custom scripts from .bashrc.d/
if [ -d ~/.bashrc.d ]; then
for i in ~/.bashrc.d/*.sh; do
[ -r "$i" ] && source "$i"
done
fi' >> ~/.bashrc
# Create bashrc.d/ drop-in directory (if not exist)
mkdir -p ~/.bashrc.d/
# Create script with export definition in drop-in directory
cat << 'EOF' > ~/.bashrc.d/variables.sh
#!/bin/bash
# Use GPG Agent as SSH agent
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
EOF
# Reload shell configuration to implement changes
source ~/.bashrc
Verify SSH Support is Active
# Verify SSH support is active
ssh-add -L
# Should display: ssh-ed25519 XXXX... cardno:0000000000
Configure SSH to Use GPG Key
- Now, just export the SSH public key from your GPG private key and add it to the remote target SSH server’s authorized_keys file to connect.
# Export the SSH public key from GPG
gpg --export-ssh-key [email protected] > ~/.ssh/yubikey.pub
# Add the SSH public key to remote servers' authorized_keys
cat ~/.ssh/yubikey.pub | ssh user@server 'cat >> ~/.ssh/authorized_keys'
Connect Using SSH
- When connecting, you will be prompted to enter the User PIN and to touch the YubiKey metal plate immediately to login.
- If you only enter the PIN and don’t touch it you will get an error similar to:
sign_and_send_pubkey: signing failed for ED25519 "cardno:32_740_587" from agent: agent refused operation.
# Login as usual via SSH
ssh user@server
Using YubiKey on New Systems
When you use a new computer or need to restore access, you only need your public key and the YubiKey.
Install Required Packages
# Install GPG and smart card tools
sudo dnf install gnupg2 pinentry-gnome3 pcsc-lite pcsc-lite-ccid
# Install YubiKey management tools (if not already installed)
sudo dnf install yubikey-manager
# Enable and start the PC/SC Smart Card daemon
sudo systemctl enable --now pcscd
Import Public Key
- First import your GPG public key from the backup or from a keyserver (if you uploaded it - I recommend https://keys.openpgp.org/).
# Import your public key from a file
gpg --import gpg_public_key.asc
# Or get from keyserver (if you uploaded it)
gpg --keyserver hkps://keys.openpgp.org --recv-keys KEY_ID_OR_EMAIL
# Trust the key (set to ultimate trust because its your own key)
gpg --edit-key KEY_ID_OR_EMAIL
gpg> trust
# Choose: 5 = I trust ultimately
gpg> quit
Test YubiKey GPG Operations
Verify YubiKey is Detected
- If it fails, check the troubleshooting section.
# Verify YubiKey is detected
gpg --card-status
Test Signing Operation
# Test signing
echo "test" | gpg --clearsign
# You should be prompted for:
# 1. User PIN
# 2. Touch on YubiKey (if enabled)
Test Encryption and Decryption Operations
# Test encryption and decryption (to yourself)
echo "secret" | gpg --encrypt -a --recipient [email protected] > test.asc
# Decrypt the new encrypted file
gpg --decrypt test.asc
# Remove the test file
rm test.asc
Test SSH (if configured)
# List SSH keys managed by GPG agent
ssh-add -L
# Expected output:
# ssh-ed25519 XXXXXXXXXXXXXXXXXXXXXXXX... cardno:000000
# Test SSH connection to a server (remote server needs the public key)
ssh -v user@remote-server
# You should be prompted for:
# 1. User PIN (first use only, then cached)
# 2. Touch on YubiKey (if enabled)
Troubleshooting Issues
During my first setup, I encountered an issue where the GPG card was not found and the gpg --card-status command returned the error below.
gpg --card-status
# Command output:
# gpg: selecting card failed: No such device
# gpg: OpenPGP card not available: No such device
First, check the status of the pcscd service. If it shows a permission issue, you have to update polkit to allow the current user access.
# Check the stauts of pcscd service
systemctl status pcscd
# Command output:
# ... systemd[1]: Started pcscd.service - PC/SC Smart Card Daemon.
# ... pcscd[2224]: 00000000 ../src/auth.c:166:IsClientAuthorized() Process 2162 (user: 60578) is NOT authorized for>
# ... pcscd[2224]: 00000224 ../src/winscard_svc.c:357:ContextThread() Rejected unauthorized PC/SC client
# ... pcscd[2224]: 00022773 ../src/auth.c:166:IsClientAuthorized() Process 2162 (user: 60578) is NOT authorized for>
# ... pcscd[2224]: 00000384 ../src/winscard_svc.c:357:ContextThread() Rejected unauthorized PC/SC client
# Create a polkit rule to allow your user access to pcscd
sudo vim /etc/polkit-1/rules.d/99-pcscd.rules
# Add the below to the 99-pcscd.rules file
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_pcsc" ||
action.id == "org.debian.pcsc-lite.access_card") {
return polkit.Result.YES;
}
});
# Set more secure permissions to the file
sudo chmod 644 /etc/polkit-1/rules.d/99-pcscd.rules
# Restart polkit and pcscd to implement the changes
sudo systemctl restart polkit.service
sudo systemctl restart pcscd.service
# Test again (should work now)
gpg --card-status
By default, the pcscd.service is activated via a systemd socket. Make sure to start the socket automatically at boot.
# Enable and start the pcscd socket
sudo systemctl enable --now pcscd.socket
What’s Next?
YubiKeys are really useful and really simple to setup. The only concern is losing the key, so my recommendation is to always have a backup in a secure location and use PINs for every operation.
Deep dives on GPG keys might come in the future but it’s pretty simple as well. Next topics might be proxychains, Docker/Podman container-related topics, or self-hosting.