A system for generating GPG keys on an air-gapped machine and loading them onto multiple YubiKey hardware tokens, taking backup on encrypted flash drive and a paper-key
YubiGPG is a semi-vibe-coded project which gives you a complete, scripted and an interactive workflow to generate a GPG (GNU Privacy Guard) master key in the most secure environment possible — an air-gapped Tails OS machine — and then distribute your subkeys across three YubiKey hardware tokens. After that, a single script configures your daily macOS or Linux machine to use the YubiKey for GPG signing, encryption, and SSH authentication.
P.S - This will not affect your Yubikey functionality for 2FA/MFA (TOTP, WebAuthn, FIDO etc), this will just flash your GPG keys for authentication, signing and encryption to your Yubikey and no they can never leave it, but can surely be replaced in future when they expired or when you voluntrily want to replace them with a new one.
- Why This Exists
- Security Model
- What You Will End Up With
- Hardware Checklist
- Key Architecture
- Getting Started — Phase 1 (Preparation)
- Phase Overview
- Script Reference
- Configuration Files
- Documentation
- Glossary
- Troubleshooting
- Emergency Procedures
- Future Maintenance — Phase 4
- What Can Be Shared Publicly
- Quick Reference Cheat Sheet
- Contributing
- Inspiration and Credits
- License
Most people who set up GPG do it on their everyday computer. They generate their master key, add their subkeys, and keep everything in ~/.gnupg. This works, but it means:
- Your master key (the key that controls your entire cryptographic identity) lives on a machine that is connected to the internet, runs untrusted software, and could be compromised at any time.
- If your machine is hacked, your master key is stolen. An attacker can forge signatures, decrypt your past messages, and impersonate you on keyservers — forever, unless you have a revocation certificate ready.
- If you lose your laptop, your master key is gone (or in an attacker's hands).
An air-gapped machine is one that has never touched a network during the key generation session. Tails OS (The Amnesic Incognito Live System) is designed for exactly this purpose: it runs entirely from RAM, leaves no traces on the machine it boots from, and makes it easy to verify that no network interfaces are active.
By generating your GPG master key on an air-gapped Tails machine:
- The master key is never exposed to any network, ever.
- Even if your daily machine is completely compromised, the attacker cannot reach your master key — it never existed there.
- The Tails session is amnesic: when you shut it down, nothing is written to the machine's storage.
A YubiKey is a small USB device that stores cryptographic keys in hardware. Keys loaded onto a YubiKey cannot be extracted from it. When you use a YubiKey for GPG:
- The private key never leaves the device. Your computer sends the data to be signed or decrypted to the YubiKey, the YubiKey does the operation internally, and returns only the result.
- If your daily machine is compromised while the YubiKey is plugged in, an attacker can use it to sign things only until you unplug it. They cannot steal the key itself.
- Touch policy enforcement means the YubiKey requires a physical button press for every cryptographic operation. Malware cannot silently use the key in the background.
- If you lose the YubiKey, it is protected by a PIN. After a small number of wrong PIN attempts, the key becomes blocked.
Hardware can fail. YubiKeys can be lost, stolen, or damaged. This setup uses three:
- KEY-1 (daily carry): Plugged into your computer during normal use.
- KEY-2 (home backup): Stored in a safe at home. Grab it if the daily carry is lost.
- KEY-3 (offsite backup): Stored at a bank or with a trusted person. Last-resort recovery if both others are gone.
Since all three hold the same subkeys, you can switch between them at any time without re-configuring your machine.
| Threat | Defense |
|---|---|
| Compromised daily machine | Subkeys live on YubiKey, never on disk. Master key never touches the daily machine. |
| Stolen YubiKey | PIN protection + touch requirement. After wrong PIN attempts, card blocks. Key cannot be extracted. |
| Lost LUKS backup USB | Second USB covers the loss. Paper backup covers both USBs being lost. |
| Nosy software on daily machine | Subkey operations require physical touch on the YubiKey. |
| Expired subkeys | Scripts 11 and 12 handle extending expiry from the air-gapped backup. |
| Accidental key destruction during YubiKey loading | Script 07 re-imports from backup before each keytocard run. |
Being honest about the limits of this system:
| Threat | Why It Is Not Covered |
|---|---|
| Evil Maid attack | If someone physically modifies the machine before you boot Tails on it, all bets are off. Use hardware you trust. |
| Tails OS compromise | This setup trusts Tails. Download Tails only from tails.net and verify the signature. |
| Forgotten passphrase | The master key backups are encrypted with your passphrase. No passphrase = no access. There is no recovery. |
| Both LUKS USBs AND paper backup lost | If all three backup methods are gone, the master key is unrecoverable. Keep backups in different physical locations. |
| User errors during key generation | Scripts have safety checks, but they cannot prevent all operator mistakes. Read each script prompt carefully. |
| Rubber hose attack | If someone compels you to reveal your passphrase, this system does not help. |
| Harvest-now-decrypt-later / CRQC | Current YubiKey firmware and GnuPG do not support post-quantum algorithms (e.g., ML-KEM, ML-DSA). If a Cryptographically Relevant Quantum Computer (CRQC) becomes available, encrypted data captured today could be decrypted retroactively. See filippo.io/crqc-timeline for the current threat assessment. |
After completing all four phases, you will have:
On your person / in your bag:
- 1 YubiKey (KEY-1, daily carry) with your GPG subkeys loaded (Sign, Encrypt, Authenticate)
- Your daily macOS or Linux machine configured to use it for Git signing, GPG, and SSH
In secure physical storage:
- 1 YubiKey (KEY-2) with the same subkeys, stored in a home safe
- 1 YubiKey (KEY-3) with the same subkeys, stored offsite
- 2 separate LUKS2-encrypted USB drives, each containing a full backup of your master secret key, all subkeys, and your revocation certificate — protected by a strong passphrase
- 1 paper printout of your master secret key in hex format (via
paperkey), which can reconstruct the key if both USBs are lost
Published / importable:
- Your GPG public key uploaded to keyservers, GitHub, or wherever you choose
- Your SSH public key (derived from the GPG authentication subkey) added to servers
Key hierarchy:
- 1 master key [C] (ed25519, no expiry) — certify only, stays air-gapped forever
- 3 subkeys (Sign [S] ed25519, Encrypt [E] cv25519, Authenticate [A] ed25519) — all 1-year expiry, all loaded on all 3 YubiKeys with mandatory touch policy
Gather everything before you start. You cannot pause mid-session on an air-gapped machine to go shopping.
| Item | Notes |
|---|---|
| 3x YubiKey 5 series | USB-A or USB-C. Firmware 5.2.3 or later strongly recommended for ed25519 support. The YubiKey 5C NFC (USB-C with NFC) is a popular daily carry choice. |
| 1x Tails OS boot USB | 8 GB or larger. This is the USB you will boot Tails from. |
| 1x Kit transfer USB | Any size. Used to copy the YubiGPG scripts and configs from your networked machine to Tails. |
| 2x LUKS backup USBs | Any size, 1 GB is more than enough. These will hold your encrypted key backups. Use different brands if possible — if one batch has a manufacturing defect, the other is likely fine. |
| 1x Printer | For printing the paperkey hex backup. A laser printer is preferable (ink does not fade). |
| A bootable x86_64 machine | Tails requires an Intel or AMD x86_64 machine. It does not run on Apple Silicon (M-series Mac). You can use an old laptop, a desktop, or any machine where you can boot from USB. |
| Your daily machine | macOS or Linux. This is where you will run the daily machine setup script (script 10) after the Tails session. |
- YubiKey: Purchase directly from Yubico (yubico.com) or an authorized reseller. Do not buy from third-party sellers on marketplaces — counterfeit or tampered YubiKeys exist.
- USB drives: Brand-name drives (SanDisk, Samsung, Kingston) are preferable for backup purposes. Avoid cheap no-name drives for the LUKS backup role.
- Tails USB: Any USB 3.0 drive of 8 GB or more works. Speed matters here — a faster drive means Tails boots faster.
- A USB hub, in case your machine has limited USB ports during the Tails session (you may need to plug in the Kit USB, up to 3 YubiKeys in sequence, and up to 2 backup USBs).
- A USB-A to USB-C adapter, if your YubiKeys have USB-C connectors and your Tails machine only has USB-A ports (or vice versa).
Before generating any keys, check which firmware version your YubiKey is running — this determines which key algorithm you can use.
How to check (on any networked machine before the Tails session):
# Using YubiKey Manager CLI:
ykman info
# Or via Yubico Authenticator (GUI): Devices → select your key → firmware version shown
# Or via GPG after inserting the YubiKey:
gpg --card-status | grep Versioned25519 vs RSA — which should you pick?
| Algorithm | Firmware required | Key size | Speed | Notes |
|---|---|---|---|---|
| ed25519 / cv25519 | ≥ 5.2.3 | 256-bit | Fast | Modern elliptic-curve. Default in these scripts. |
| RSA 4096 | Any YubiKey 5 | 4096-bit | Slower | Larger keys, but works on all firmware versions. |
- ed25519 is a modern elliptic-curve algorithm. A 256-bit ed25519 key offers roughly equivalent security to a 3072-bit RSA key, with faster operations and smaller key material. It is what these scripts generate by default.
- RSA 4096 is the traditional choice. Keys are larger and operations are slower, but it is universally supported and perfectly secure. Choose this if your firmware is below 5.2.3.
Decision:
- Firmware ≥ 5.2.3 (all YubiKey 5 series purchased after 2019): use ed25519/cv25519 — follow these scripts as written.
- Firmware < 5.2.3 (older hardware): use RSA 4096 — when GPG prompts you to select a key type during scripts 02 and 03, choose RSA instead of Curve 25519 and enter
4096as the key size.
If you are unsure or your hardware is old, replace it before starting. All current YubiKey 5 series ship with firmware well above 5.2.3. Purchase directly from yubico.com.
+-----------------------------------------+
| GPG Master Key [C] |
| ed25519 | No expiry | Certify only |
| NEVER touches a networked machine |
+-----------------------------------------+
|
+-------------------+-------------------+
| | |
+--------+-------+ +--------+-------+ +-------+--------+
| Subkey [S] | | Subkey [E] | | Subkey [A] |
| ed25519 | | cv25519 | | ed25519 |
| Sign | | Encrypt | | Authenticate |
| 1yr expiry | | 1yr expiry | | 1yr expiry |
+----------------+ +----------------+ +---------------+
| | |
+---------+---------+---------+---------+---------+
| | |
+-----------+ +-----------+ +-----------+
| YubiKey | | YubiKey | | YubiKey |
| KEY-1 | | KEY-2 | | KEY-3 |
| Daily | | Home safe | | Offsite |
| carry | | | | backup |
+-----------+ +-----------+ +-----------+
Master Key Backups (3 independent methods)
============================================
Method 1: LUKS USB #1 Method 2: LUKS USB #2
+---------------------------+ +---------------------------+
| LUKS2-encrypted volume | | LUKS2-encrypted volume |
| Passphrase-protected | | Passphrase-protected |
| | | |
| master-secret-key.asc | | master-secret-key.asc |
| subkeys-secret.asc | | subkeys-secret.asc |
| public-key.asc | | public-key.asc |
| revocation-cert.asc | | revocation-cert.asc |
+---------------------------+ +---------------------------+
Store: Home safe Store: Offsite / bank
Method 3: Paper backup
+---------------------------+
| Printed hex data |
| (paperkey format) |
| Requires: public key + |
| passphrase to reconstruct |
+---------------------------+
Store: Fireproof safe or
safety deposit box
| Key | Capability | Real-World Use |
|---|---|---|
| Master [C] | Certify | Signs other people's keys (web of trust), creates and revokes subkeys |
| Subkey [S] | Sign | git commit signatures, gpg --detach-sign on files and releases |
| Subkey [E] | Encrypt | gpg --encrypt for files and emails sent to you |
| Subkey [A] | Authenticate | SSH into servers — replaces ~/.ssh/id_ed25519 entirely |
Phase 1 happens on a normal networked machine (your daily computer). You need internet access for this phase.
The easiest way is to grab the latest release — a pre-packaged archive ready to copy straight to your transfer USB:
- Go to the latest release
- Download
gpg-kit-v1.0.0.tar.gz - Verify before use (do not skip this):
# Verify the checksum
shasum -a 256 -c gpg-kit-v1.0.0.tar.gz.sha256
# Verify the GPG signature (import the maintainer's public key first)
gpg --verify gpg-kit-v1.0.0.tar.gz.asc gpg-kit-v1.0.0.tar.gz- Extract the archive:
tar -xzf gpg-kit-v1.0.0.tar.gz
# You now have a gpg-kit/ folder — this is what goes on the USBAlternatively, clone the repo directly:
git clone https://github.com/iayanpahwa/YubiGPG.git
cd YubiGPG- Go to https://tails.net (the official site — be careful of typos).
- Follow the official download instructions for your operating system.
- Verify the signature. Tails provides detailed instructions for this. Do not skip verification — it is the only way to confirm you have a genuine Tails image.
Follow the official Tails installation instructions. On macOS, Tails provides a graphical installer. On Linux, you can use the dd command or a tool like Balena Etcher.
The Tails boot USB will be reformatted during this process. Do not use a USB that has data you need to keep. Set a sudo / administrator password during boot.
Insert a separate USB drive (not the Tails boot USB — a different one). Format it as FAT32 or exFAT so Tails can read it.
If you downloaded the release archive, the extracted gpg-kit/ folder is already structured correctly — just copy it to the USB:
# On macOS
cp -r gpg-kit/ /Volumes/YOUR_USB/gpg-kit
# On Linux
cp -r gpg-kit/ /media/YOUR_USERNAME/USBNAME/gpg-kitIf you cloned the repo, copy the repo contents into a gpg-kit/ folder on the USB:
# On macOS
cp -r /path/to/YubiGPG /Volumes/YOUR_USB/gpg-kit
# On Linux
cp -r /path/to/YubiGPG /media/YOUR_USERNAME/USBNAME/gpg-kitConfirm the directory looks like this on the USB:
gpg-kit/ (this github repo)
├── configs/
│ ├── gpg.conf
│ ├── gpg-agent.conf
│ └── gpg-ssh-env.sh
├── scripts/
│ ├── 01-tails-setup.sh
│ ├── 02-generate-master.sh
│ ... (all 12 scripts)
└── docs/
└── PAPER-RECOVERY.md
Before the Tails session, change the default PINs on each YubiKey. The factory defaults are:
- User PIN:
123456 - Admin PIN:
12345678 - Reset Code: (not set by default)
You can change PINs from your daily machine before the Tails session:
# Install ykman if not already installed
# macOS:
brew install ykman
# Then change PINs (do this for each YubiKey)
gpg --card-edit
# At the gpg/card> prompt:
# admin
# passwd
# Choose option 1 to change user PIN
# Choose option 3 to change admin PIN
# quitChoose strong, memorable PINs. You will need the User PIN for every cryptographic operation. You will need the Admin PIN for YubiKey configuration. If you forget the Admin PIN and the User PIN is blocked, the only recovery is a full YubiKey reset, which destroys the keys on the card.
Write down your PINs temporarily and store them securely until you have them memorized.
Gather everything you need before you sit down at the air-gapped machine:
- Tails boot USB
- Kit transfer USB (with YubiGPG scripts)
- 2 LUKS backup USBs (empty, or data you are willing to destroy)
- 3 YubiKeys (with PINs changed from defaults)
- Printer connected and ready (for paper backup)
- A strong passphrase in your head — this will protect the master key backup. Use a memorable passphrase of at least 6 random words (diceware style). Write it down temporarily.
- Your name and email address (will be embedded in the GPG key)
You are now ready for Phase 2.
Boot the Tails OS USB on your air-gapped machine. Set an admin password when prompted (you will need it to run commands as root). Do NOT connect to a network.
Run the scripts in order. Each script will tell you what to do next.
01-tails-setup.sh Verify air gap, install gpg.conf, start pcscd
|
02-generate-master.sh Create ed25519 master key (enter name, email, passphrase)
|
03-generate-subkeys.sh Add Sign, Encrypt, Authenticate subkeys (enter expiry)
|
04-export-keys.sh Export everything to /tmp/gpg-export/
|
05-backup-to-luks.sh Run TWICE — once per backup USB
|
06-paper-backup.sh Generate hex backup, copy to USB for printing
|
07-yubikey-transfer.sh Run THREE TIMES — one per YubiKey (auto-restores between runs)
|
08-key-summary.sh Review all output before destroying anything
|
09-cleanup.sh Secure wipe all key material, shutdown Tails
Back on your normal daily machine (macOS or Linux):
10-daily-machine-setup.sh Install packages, import public key, configure SSH/Git, test YubiKey
This is a one-time setup. After this, your daily machine is ready.
When subkeys approach expiry, or when you need to revoke a compromised key, boot Tails again and run:
01-tails-setup.sh Re-establish air-gapped environment
|
11-restore-from-luks.sh Decrypt a backup USB and import the master key
|
12-manage-expiry.sh Extend expiry, revoke, or regenerate subkeys
|
07-yubikey-transfer.sh If new subkeys were generated, load them on all 3 YubiKeys
|
09-cleanup.sh Wipe and shutdown
| Script | Purpose | Phase | How Many Times to Run |
|---|---|---|---|
01-tails-setup.sh |
Verify air gap is active, install gpg.conf to ~/.gnupg/, start the pcscd smart card daemon so Tails can talk to YubiKeys |
Start of every Tails session | Once per session (or more if needed) |
02-generate-master.sh |
Interactively create the ed25519 master [C] key with your name and email | Key generation only | Once, ever |
03-generate-subkeys.sh |
Add the three subkeys: Sign [S] ed25519, Encrypt [E] cv25519, Authenticate [A] ed25519 | Key generation only | Once, ever |
04-export-keys.sh |
Export master secret key, subkeys secret, public key, and revocation certificate to /tmp/gpg-export/ |
Key generation only | Once |
05-backup-to-luks.sh |
Create a LUKS2-encrypted volume on a USB drive and copy all exported key material into it | Key generation only | Twice (run once per backup USB) |
06-paper-backup.sh |
Use paperkey to produce a hex representation of the master secret key, copy it to the kit USB for printing |
Key generation only | Once |
07-yubikey-transfer.sh |
Load subkeys onto one YubiKey using keytocard. Automatically re-imports from backup before each run so local keys survive the destructive transfer. |
Key generation + after subkey regen | Three times (once per YubiKey) |
08-key-summary.sh |
Display a full summary of what exists: key fingerprints, YubiKey card status, what files are on the LUKS USBs | Before cleanup | Once |
09-cleanup.sh |
Securely wipe /tmp/gpg-export/ and ~/.gnupg/, present a final checklist, and shut down Tails |
End of every Tails session | Once per session |
10-daily-machine-setup.sh |
Install gnupg, pinentry-mac (macOS), configure ~/.gnupg/gpg-agent.conf, import the public key, configure Git signing, add SSH env to shell RC |
After first Tails session | Once (on daily machine) |
11-restore-from-luks.sh |
Mount a LUKS backup USB, decrypt it, import the master secret key into Tails' GPG keyring | Future maintenance | As needed |
12-manage-expiry.sh |
Interactively extend key expiry dates, revoke subkeys, or generate replacement subkeys | Future maintenance | As needed |
The configs/ directory contains three files that are installed onto the appropriate machines by the scripts. Here is what each one does and where it ends up.
Installed to: ~/.gnupg/gpg.conf on the Tails machine (by script 01)
This file configures GPG's cryptographic preferences in hardened mode. It tells GPG to prefer the strongest available algorithms and to never output version strings (which could reveal your software version to attackers).
Key settings:
personal-cipher-preferences AES256 AES192 AES— When encrypting, prefer AES-256. AES-256 is considered unbreakable with current technology.personal-digest-preferences SHA512 SHA384 SHA256— When hashing (signing, key certification), prefer SHA-512. SHA-512 produces a 512-bit hash that is computationally infeasible to reverse or collide.cert-digest-algo SHA512— All key certifications (signatures on keys) must use SHA-512.s2k-digest-algo SHA512ands2k-cipher-algo AES256— The string-to-key function (which derives an encryption key from your passphrase) uses SHA-512 and AES-256. This means your passphrase is protected by the strongest available algorithms.keyid-format 0xlong— Display long (64-bit) key IDs, which are much harder to fake than short (32-bit) IDs.with-fingerprint— Always display the full 160-bit fingerprint, not just the key ID.no-commentsandno-emit-version— Do not include "Comment:" or "Version:" headers in exported key blocks or signatures. These headers leak information.no-auto-key-locate— Do not automatically fetch keys from keyservers. You decide when to fetch.
Installed to: ~/.gnupg/gpg-agent.conf on your daily machine (by script 10)
The GPG agent (gpg-agent) is a background process that manages your private keys and PIN caching. On a YubiKey setup, it also acts as an SSH agent.
Key settings:
enable-ssh-support— Tellsgpg-agentto expose an SSH agent socket. Your shell will pointSSH_AUTH_SOCKto this socket, which makes all SSH commands automatically use the authentication subkey on your YubiKey.default-cache-ttl 600— Cache the PIN for 10 minutes after each GPG operation. You will not be re-prompted for the PIN on every single signing operation within a 10-minute window.max-cache-ttl 7200— The cached PIN expires after 2 hours of inactivity at most.default-cache-ttl-ssh 600andmax-cache-ttl-ssh 7200— Same caching behavior for SSH operations.pinentry-program— The script uncomments the correct line for your OS. On macOS,pinentry-macprovides a native graphical dialog for PIN entry. On Linux,pinentry-gnome3(GUI) orpinentry-curses(terminal) are available.
Appended to: ~/.zshrc or ~/.bashrc on your daily machine (by script 10)
This shell environment file configures every new terminal session to route SSH through gpg-agent.
Key sections:
export GPG_TTY=$(tty)— Tells GPG which terminal to use for PIN prompts in terminal-mode pinentry. Required for pinentry to work correctly.- macOS SSH agent override — On macOS, the system starts its own SSH agent via
launchd. This script unsetsSSH_AGENT_PIDto prevent confusion between the system agent andgpg-agent. export SSH_AUTH_SOCK— Points togpg-agent's SSH socket. Every time you runssh, it will talk togpg-agent, which in turn talks to your YubiKey.gpgconf --launch gpg-agent— Ensures the agent is running at the start of every terminal session.gpg-connect-agent updatestartuptty /bye— Tellsgpg-agentwhich terminal is the current one. This fixes a common macOS issue where the PIN prompt appears on a stale, closed terminal window after sleep/wake.- Aliases:
gpg-ssh-pubkey,gpg-card,gpg-restart,gpg-list— convenience shortcuts for common operations.
This section explains every technical term used in this project in plain English.
A physical security measure where a computer has no network connections — no Wi-Fi, no Ethernet, no Bluetooth. An air-gapped machine cannot send or receive data over a network. In this project, Tails OS is run on an air-gapped machine so that no malware on the internet can observe or steal the GPG master key during generation.
One of the three GPG subkeys generated in this project. The authentication subkey is used to prove your identity — specifically, to authenticate SSH sessions. Instead of generating a separate ~/.ssh/id_ed25519 key pair, your SSH clients use the authentication subkey stored on your YubiKey. This means SSH access to all your servers is protected by the YubiKey's hardware and PIN.
Alternative — FIDO2 SSH keys: For many users, hardware-backed SSH via FIDO2 (ssh-keygen -t ed25519-sk) is simpler to set up and is natively supported by OpenSSH ≥ 8.2 and modern YubiKeys. If SSH is your primary use case and you do not need GPG signing or encryption, FIDO2 SSH may be a better fit. This guide uses GPG-for-SSH to keep everything on one key with one trust anchor.
An elliptic-curve Diffie-Hellman algorithm used for encryption. "cv" stands for Curve25519, which is a well-analyzed, modern elliptic curve designed by cryptographer Daniel J. Bernstein. It is used for the encryption subkey [E] in this project. Note: signing and authentication use ed25519, while encryption uses cv25519 — these are related but different algorithms built on the same underlying curve.
An elliptic-curve digital signature algorithm. "ed" stands for Edwards-curve Digital Signature Algorithm on Curve25519. It produces 64-byte signatures, is very fast, and has a strong security track record. This project uses ed25519 for the master key, signing subkey [S], and authentication subkey [A].
A family of cryptographic algorithms based on the mathematics of elliptic curves over finite fields. ECC keys are much shorter than RSA keys for the same security level — a 256-bit ECC key provides similar security to a 3072-bit RSA key. Both ed25519 and cv25519 are ECC algorithms.
One of the three GPG subkeys. When someone wants to send you an encrypted message or file, they use your public key's encryption subkey to encrypt it. Only your YubiKey (which holds the corresponding private subkey) can decrypt it.
A background daemon (long-running process) that manages GPG private keys and PIN caching. After you enter your PIN once, gpg-agent caches it for the configured TTL (time to live) so you are not asked on every operation. In this project, gpg-agent is also configured as an SSH agent, handling SSH authentication through the YubiKey's authentication subkey.
A GPG command that moves a private key from the local GPG keyring onto a smart card (like a YubiKey). The key word is "moves" — after keytocard runs, the local copy of the private key is replaced by a stub that points to the card. The key is now on the hardware and cannot be extracted. This is why script 07 must re-import from the backup before loading each YubiKey — the first keytocard call would otherwise destroy the local copy before the other two YubiKeys are loaded.
The standard disk encryption specification for Linux. LUKS2 (version 2) is the current standard. In this project, scripts create a LUKS2-encrypted volume on each backup USB drive. The volume is protected by a passphrase. Without the correct passphrase, the data on the drive is computationally indistinguishable from random noise — it cannot be read.
The root of your GPG identity. The "C" stands for Certify. The master key's only function is to:
- Sign (certify) other people's public keys (establishing web of trust relationships)
- Create and revoke your own subkeys
The master key itself is never used for signing files, encrypting messages, or authenticating SSH sessions. Those operations use subkeys. This design means the master key can stay air-gapped forever, while subkeys (which expire) can be rotated without changing your public identity.
A tool (and format) for exporting the secret parts of a GPG key as a hex text dump that can be printed on paper. Paperkey is designed for disaster recovery: if all digital backups are lost, you can reconstruct the secret key by typing in the hex data from the printout, combined with your public key (which is freely available). This project uses paperkey as the third backup method.
A string of words or characters used to protect your GPG secret key backups. Unlike a PIN, which is short and used for hardware access, a passphrase is longer and used to encrypt/decrypt key files. The passphrase encrypts the LUKS volumes and the GPG secret keys exported during backup. Without it, the backups are useless. There is no recovery mechanism for a forgotten passphrase.
A system service that provides communication between the operating system and smart card readers, including YubiKeys. In Tails, pcscd is not running by default and must be started manually. Script 01 handles this. Without pcscd, GPG cannot communicate with the YubiKey.
A short numeric or alphanumeric code that protects the YubiKey. You enter the User PIN every time you use the YubiKey for a cryptographic operation (signing, decrypting, or authenticating — subject to the PIN cache TTL in gpg-agent.conf). After 3 consecutive wrong attempts, the User PIN is blocked.
A longer PIN that protects YubiKey administrative functions, such as changing the User PIN, loading keys onto the card, or changing touch policy. After 3 consecutive wrong Admin PIN attempts, the card becomes permanently locked (all keys are destroyed). Do not confuse the User PIN and Admin PIN.
One of the three GPG subkeys. Used to sign files, emails, and Git commits. When you run git commit, Git calls GPG with the signing subkey, which sends the data to the YubiKey, which signs it after you touch the button.
A GPG key that is certified by and subordinate to a master key. Subkeys have their own key IDs, algorithms, and expiry dates. The public key that you share with the world contains both the master key's public portion and the subkeys' public portions. The secret portions of your subkeys are what get loaded onto the YubiKey.
The Amnesic Incognito Live System. A Debian-based Linux distribution designed to run from a USB drive with no persistent state. Tails routes all traffic through Tor when network is used. For this project, we use Tails primarily because it is amnesic (leaves no traces on the machine) and because it has GPG and relevant tools pre-installed. Tails is downloaded from https://tails.net.
A YubiKey feature that requires a physical press of the YubiKey's capacitive button before any cryptographic operation. This prevents malware from silently using the YubiKey in the background. In this project, mandatory touch policy is set on all three YubiKeys for all operations (sign, encrypt, authenticate).
A decentralized trust model in GPG where users certify each other's keys. If Alice certifies Bob's key, and you trust Alice, you can transitively trust Bob. The master key [C] in this project is used to participate in the web of trust by certifying other people's keys. This is distinct from your own subkeys, which are for your own sign/encrypt/authenticate operations.
Symptom: gpg --card-status returns "No card" or "Card not present"
Causes and fixes:
-
pcscdis not running (most common on Tails):sudo systemctl start pcscd
-
GPG agent has a stale connection:
gpg-connect-agent "scd kill" /bye gpg --card-statusOr use the alias:
gpg-restart
-
YubiKey is not recognized as a smart card (rare hardware issue):
lsusb # Check if the YubiKey appears at allIf the YubiKey appears in
lsusbbut not in GPG, try unplugging and re-inserting. -
On macOS after sleep/wake: The
gpg-agentloses its connection to the YubiKey after the machine wakes. Run:gpg-restart
-
Another process is holding the smart card (e.g., a browser extension for PIV):
sudo systemctl stop pcscd sudo systemctl start pcscd
Symptom: GPG reports "Bad PIN" or "PIN blocked"
The YubiKey allows 3 wrong User PIN attempts before blocking. The counter resets after a correct entry.
If the User PIN is blocked (3 consecutive wrong attempts), use the Admin PIN to unblock it:
gpg --card-edit
# At the gpg/card> prompt:
admin
passwd
# Choose option 4: "Unblock PIN"
# Enter Admin PIN when prompted
# Set a new User PIN
quitIf you have forgotten the User PIN but remember the Admin PIN, you can reset the User PIN using the method above.
If both PINs are wrong/forgotten:
ykman openpgp resetThis performs a full factory reset of the OpenPGP application on the YubiKey. All keys loaded on the card are permanently destroyed. After this, you will need to reload the subkeys from your LUKS backup or paper backup via script 07.
Symptom: ssh user@server fails with "Permission denied (publickey)"
Work through these checks in order:
-
Confirm your SSH public key is in
~/.ssh/authorized_keyson the server:# Get the SSH public key from the YubiKey gpg --export-ssh-key YOUR_KEY_ID # Or use the alias: gpg-ssh-pubkey
Copy this output and ensure it is in
~/.ssh/authorized_keyson the server (one line per key). -
Confirm
SSH_AUTH_SOCKpoints to gpg-agent:echo $SSH_AUTH_SOCK # Should show something like: /Users/you/.gnupg/S.gpg-agent.ssh # NOT: /tmp/launch-xxx/Listeners (which is the macOS system ssh-agent)
If it shows the macOS launcher path, your shell RC changes did not take effect. Run
source ~/.zshrcor open a new terminal. -
Check that the agent sees the key:
ssh-add -l # Should list your GPG authentication keyIf it shows "The agent has no identities", the YubiKey might not be inserted, or gpg-agent needs a restart:
gpg-restart ssh-add -l # Try again -
Test with verbose SSH output:
ssh -vvv user@server 2>&1 | grep -A2 "Offering\|Authentications"
-
On macOS, confirm the system SSH agent is not overriding:
launchctl unload -w /System/Library/LaunchAgents/com.openssh.ssh-agent.plist
This permanently disables the macOS SSH agent for your user. The
gpg-ssh-env.shhandles this viaSSH_AUTH_SOCKoverride, but some macOS versions are aggressive about restoring the system agent.
Symptom: Signing fails, or you see "gpg: signing failed: Timeout"
-
Restart the agent:
gpg-restart
-
Make sure the YubiKey is inserted and recognized:
gpg --card-status
-
If GPG shows stubs but no card, the agent still has the previous session's key stubs. This can happen if you switch between YubiKeys or if the agent started before the card was inserted:
gpg-connect-agent "scd serialno" /bye -
On macOS, check that the correct
gpgbinary is used:which gpg # Should be: /opt/homebrew/bin/gpg (Apple Silicon) or /usr/local/bin/gpg (Intel) # NOT: /usr/bin/gpg (the old macOS built-in, if any)
Symptom: No PIN dialog appears, or it appears in the background, or you see "pinentry failed" errors
-
Confirm
pinentry-macis installed:brew list | grep pinentry # Should show: pinentry-mac
If not:
brew install pinentry-mac -
Confirm
gpg-agent.confhas the correct pinentry-program line uncommented:cat ~/.gnupg/gpg-agent.conf | grep pinentry # Apple Silicon (M-series Mac): # pinentry-program /opt/homebrew/bin/pinentry-mac # Intel Mac: # pinentry-program /usr/local/bin/pinentry-mac
-
After changing
gpg-agent.conf, restart the agent:gpg-restart
-
If the dialog appears behind other windows: This is a macOS focus issue with
pinentry-mac. Try clicking the menu bar to bring it forward, or check if the dialog is on a different Space/desktop. -
If using SSH in a terminal and no dialog appears (common after macOS sleep):
gpg-connect-agent updatestartuptty /bye
This tells the agent to use the current terminal for PIN prompts. The
gpg-ssh-env.shfile runs this automatically in each new shell session, but a sleep/wake cycle can invalidate it.
Symptom: git commit fails with a GPG error, or commits are not signed
-
Confirm Git is configured to use GPG signing:
git config --global user.signingkey # Should show your key ID git config --global commit.gpgsign # Should show: true git config --global gpg.program # Should show the path to gpg (e.g., /opt/homebrew/bin/gpg)
-
Test GPG signing directly:
echo "test" | gpg --clearsign
If this works (prompts for touch, produces signed output), the issue is with Git's GPG configuration. If it fails, see the GPG Agent Issues section.
-
Confirm the signing key ID matches what is in Git config:
gpg-list # Find the [S] key line and its ID
If you lose one YubiKey (e.g., the daily carry):
- Immediately stop using that YubiKey. If it was lost rather than stolen, your keys are still protected by the PIN. If you think it was stolen, proceed to the revocation procedure below.
- Retrieve your KEY-2 or KEY-3 backup and use it instead. Your daily machine will work with any of the three YubiKeys — they all hold the same subkeys.
- Optionally, acquire a replacement YubiKey and load the subkeys onto it using the LUKS backup: Boot Tails → run script 01 → run script 11 (restore) → run script 07 (load the new YubiKey) → run script 09 (cleanup).
If your YubiKey was stolen AND you believe the attacker knows or can guess your PIN:
- Import your revocation certificate on your daily machine:
# Mount the LUKS backup USB (or retrieve from backup) gpg --import revocation-cert.asc - Publish the revocation to keyservers:
gpg --send-keys YOUR_KEY_ID # Or for keys.openpgp.org: gpg --keyserver hkps://keys.openpgp.org --send-keys YOUR_KEY_ID - Generate a new key set: Boot Tails, run scripts 02 through 09 again with a completely new key.
- Notify anyone who uses your public key that the old key is revoked and share the new one.
If you entered the User PIN wrong 3 times and the card is now blocked:
- Use the Admin PIN to unblock:
gpg --card-edit admin passwd # Select option 4: "Unblock PIN" - If you do not remember the Admin PIN, and you have run out of Admin PIN attempts: the card is permanently locked. Run
ykman openpgp resetto factory-reset the OpenPGP applet. Then restore your subkeys from the LUKS backup and load them again with script 07.
If your key is compromised, expired beyond recovery, or you are abandoning it for any reason:
-
If you have the revocation certificate:
gpg --import revocation-cert.asc gpg --keyserver hkps://keys.openpgp.org --send-keys YOUR_KEY_ID
-
If you need to generate a new revocation certificate (because you lost the one from the backup), you must have access to the master secret key. Boot Tails, restore from LUKS (script 11), then:
gpg --output new-revocation-cert.asc --gen-revoke YOUR_KEY_ID
This is the worst case: all three YubiKeys are gone, but you still have LUKS backup or paper backup.
- Boot Tails (air-gapped, as always).
- Run script 01 (
01-tails-setup.sh). - Run script 11 (
11-restore-from-luks.sh) to import the master key from a LUKS USB. Or, if both LUKS USBs are also gone, follow the procedure indocs/PAPER-RECOVERY.mdto reconstruct the master key from the paper printout. - Acquire 3 new YubiKeys and run script 07 three times to load the subkeys onto them.
- Run script 09 to clean up.
If both encrypted USB backups are gone but the paper printout survives, follow the full procedure in docs/PAPER-RECOVERY.md. The summary:
- Obtain your public key from a keyserver or anyone who has it.
- Boot Tails.
- Install
paperkey(brief network connection required). - Carefully type in the hex data from the paper printout.
- Run
paperkey --pubring public-key.gpg --secrets paperkey-data.txt --output recovered.gpgto reconstruct the secret key. - Import and proceed.
This is not recoverable. The LUKS USB backups and the paperkey file are all encrypted/protected by your passphrase. Without the passphrase, the backup data is permanently inaccessible.
If this happens and you still have your YubiKeys, the subkeys continue to work — you can still sign, encrypt, and authenticate for the remaining lifetime of the subkeys. You simply cannot extend expiry, revoke, or generate new subkeys.
Plan: generate a new key pair from scratch when the subkeys expire.
- Subkey expiry approaching: Your subkeys expire 1 year after generation. GPG will warn you when expiry is within 6 months. Extending expiry requires the master key.
- Subkey compromised: If you believe a subkey was exposed, revoke it and generate a new one.
- New YubiKey to load: If you acquire a replacement YubiKey, load it via script 07.
- PIN change needed: This can be done from the daily machine with
gpg --card-edit— no Tails session required.
- Boot Tails on the air-gapped machine.
- Insert the Kit USB (with YubiGPG scripts).
- Run
01-tails-setup.shto set up the session. - Insert a LUKS backup USB and run
11-restore-from-luks.shto import the master key. - Run
12-manage-expiry.shand follow the interactive prompts. - If new subkeys were generated, run
07-yubikey-transfer.shthree times (once per YubiKey). - Export an updated public key and distribute it (re-upload to keyservers, GitHub, etc.).
- Run
09-cleanup.shto wipe and shut down.
When you extend subkey expiry and export the updated public key, anyone who has your old public key will need to import the updated version to see the new expiry dates. Distribute via keyservers:
# On your daily machine after importing the updated public key
gpg --keyserver hkps://keys.openpgp.org --send-keys YOUR_KEY_ID| Item | Public? | Notes |
|---|---|---|
public-key.asc |
YES | Upload to GitHub, keyservers (keys.openpgp.org), your website, email signature |
| Key fingerprint | YES | Include in email signature, social media bio, business cards, anywhere |
| SSH public key | YES | Add to all servers' ~/.ssh/authorized_keys, GitHub SSH keys, etc. |
master-secret-key.asc |
NEVER | Anyone who obtains this + your passphrase can impersonate you completely |
subkeys-secret.asc |
NEVER | Allows sign, decrypt, and SSH authentication as you |
revocation-cert.asc |
NEVER | Can be used to destroy your key's reputation on keyservers. Guard it. |
PAPER-BACKUP-PRINT-ME.txt |
NEVER | Contains the secret key in hex (needs public key + passphrase to reconstruct, but still) |
| YubiKey User PIN | NEVER | Physical access protection |
| YubiKey Admin PIN | NEVER | Allows full card reconfiguration |
| GPG passphrase | NEVER | Protects all key backups. If disclosed, all backups are compromised. |
Once set up, these are the day-to-day commands you will use:
# Git signing (automatic when configured)
git commit -m "your message"
# Sign a file
gpg --armor --detach-sign file.tar.gz
# Produces: file.tar.gz.asc
# Verify a signature
gpg --verify file.tar.gz.asc file.tar.gz
# Encrypt a file to yourself
gpg --armor --recipient YOUR_KEY_ID --encrypt file.txt
# Produces: file.txt.asc
# Encrypt a file to someone else
gpg --armor --recipient THEIR_KEY_ID --encrypt file.txt
# Decrypt a file
gpg --decrypt file.txt.asc > file.txt
# SSH (just works after setup — no extra commands needed)
ssh user@server.example.com
# Get your SSH public key from the YubiKey
gpg --export-ssh-key YOUR_KEY_ID
# Or use the alias:
gpg-ssh-pubkey
# Check YubiKey status
gpg --card-status
# Or:
gpg-card
# Restart gpg-agent (fixes most "YubiKey not responding" issues)
gpg-restart
# List your secret keys
gpg --list-secret-keys --keyid-format 0xlong
# Or:
gpg-list
# Find your key fingerprint
gpg --fingerprint YOUR_EMAILContributions are welcome. Please open an issue before submitting a pull request for significant changes, so the approach can be discussed.
Areas where contributions are especially useful:
- Testing on specific Linux distributions and reporting compatibility issues
- Improvements to error handling in the shell scripts
- Additional troubleshooting scenarios
- Documentation clarifications
Please read CONTRIBUTING.md for contribution guidelines, code style, and the pull request process.
This project was inspired by and built upon the community knowledge in:
drduh/YubiKey-Guide — The de facto community reference for setting up GPG with a YubiKey. If you want to understand the underlying concepts in exhaustive detail, or if you prefer to run each command manually rather than using scripts, the YubiKey-Guide is the place to start. YubiGPG is an opinionated, scripted implementation of a subset of that guide, with specific choices made for the ed25519/cv25519 algorithm set, a three-YubiKey setup, and an air-gapped Tails workflow.
Additional references:
- Tails OS Documentation — Official Tails documentation
- GnuPG Manual — Official GPG reference
- YubiKey 5 Technical Manual — YubiKey firmware and hardware reference
- paperkey — The tool used for the paper backup format
Copyright 2026 YubiGPG Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.