GPG: A Practical Guide, Basics to Advanced¶
GPG (GNU Privacy Guard) is an implementation of the OpenPGP standard for
public-key cryptography. It's the backbone of pass, signed git commits,
encrypted email, and a lot of "prove this file came from me and hasn't
been altered" tooling across Linux. This guide goes from first principles
through to the kind of purpose-scoped, automation-friendly key setup you
need for something like an unattended backup script.
1. Core concepts¶
GPG uses asymmetric cryptography: every identity has a key pair — a private key you never share, and a public key you hand out freely.
- Encrypt to someone: use their public key. Only their private key can decrypt it.
- Sign something: use your private key. Anyone with your public key can verify the signature came from you and the content wasn't altered.
A few terms that come up constantly:
- Keyring — your local database of keys (yours and others').
- Key ID / fingerprint — identifiers for a key. The fingerprint (a 40-character hex string) is the only one you should trust; short key IDs (8 hex chars) have been forged in the wild.
- UID (User ID) — the name/email attached to a key, e.g.
Andrew Mercer <[email protected]>. A key can have several. - Trust — GPG's model for deciding how much to believe a UID→key binding. This matters for verifying other people's keys; for your own keys and service automation it's mostly not relevant.
- Web of Trust (WoT) — a decentralized trust model where people sign each other's keys. Largely academic for personal/homelab use; most people today rely on out-of-band fingerprint verification (checking a fingerprint over a second channel) instead.
2. Installing GPG¶
Almost certainly already present on any Linux system, but if not:
# Debian/Ubuntu
sudo apt install gnupg
# Fedora/RHEL
sudo dnf install gnupg2
# Arch
sudo pacman -S gnupg
Check the version — GPG 2.x is what you want (1.x is ancient):
gpg --version
3. Generating your first key¶
gpg --full-generate-key
You'll be prompted for:
- Key type — RSA and RSA (default) is fine unless you have a reason to use ECC (Curve 25519 — smaller, faster, modern, but slightly less universally supported by older tooling).
- Key size — 4096 for RSA.
- Expiration — don't pick "never" for anything you'll actually rely on long-term. 1–2 years is common; you renew before it lapses (§8 covers rotation). An expired key stops working until you extend it, but it's not destroyed — recoverable, unlike a compromised key.
- Name/email — becomes your UID.
- Passphrase — protects the private key at rest. This is the piece that becomes the whole problem for automation (§9).
4. Basic operations¶
List your keys:
gpg --list-secret-keys --keyid-format long
Encrypt a file for someone (using their public key):
gpg --encrypt --recipient [email protected] file.txt
# produces file.txt.gpg
Decrypt something sent to you:
gpg --decrypt file.txt.gpg > file.txt
Sign a file (produces a detached signature, leaving the original file untouched):
gpg --detached-sign file.txt
# produces file.txt.sig
Verify a signature:
gpg --verify file.txt.sig file.txt
Symmetric encryption (no key pair involved — just a shared passphrase, useful for "encrypt this for myself/for someone I'll share a password with out of band"):
gpg --symmetric file.txt
5. Sharing and importing keys¶
Export your public key to share:
gpg --armor --export [email protected] > pubkey.asc
Import someone else's:
gpg --import theirkey.asc
Publish to a keyserver (optional, and worth knowing modern keyservers like keys.openpgp.org require confirming your email address before your UID becomes visible — this exists specifically to stop the old keyserver spam/harassment problem):
gpg --keyserver keys.openpgp.org --send-keys YOUR_FINGERPRINT
6. Backups and revocation¶
This is the step people skip and regret. If your private key is lost (disk failure, wiped laptop) with no backup, everything encrypted to it is gone permanently — there's no recovery mechanism, by design.
Back up the private key:
gpg --export-secret-keys --armor [email protected] > private-key-backup.asc
Store this somewhere genuinely offline — encrypted USB drive in a safe, not a cloud drive, not this machine's home directory next to the key it's backing up.
Generate a revocation certificate — this lets you tell the world "this key is dead" if it's ever compromised, without needing the private key itself (generate it now, while you still have access, and store it alongside the backup):
gpg --gen-revoke [email protected] > revoke-cert.asc
7. Subkeys — the mechanism that makes everything else possible¶
By default, --full-generate-key actually creates a primary key
(capable of certifying — i.e. signing other keys) plus one subkey
(used for encryption). This split is the foundation of good GPG hygiene:
- The primary key is your identity's root of trust. Losing it means losing everyone's trust in your UID and needing a whole new identity.
- Subkeys can be independently revoked and replaced without affecting your primary key or requiring everyone to re-trust you.
View your subkeys:
gpg --list-secret-keys --keyid-format long [email protected]
Output shows sec (primary, certify capability) and ssb (subkey,
usually encrypt) lines.
Add a signing or authentication subkey:
gpg --edit-key [email protected]
gpg> addkey
# choose signing, authentication, or encryption as needed
gpg> save
The advanced move: take the primary key offline entirely. Once subkeys exist for signing/encryption/auth, you can export just the subkeys to your daily-driver machine and move the primary private key to offline cold storage (an encrypted USB drive, powered off between uses). Day-to-day signing and decryption work exactly as before via the subkeys; if this laptop is compromised, the attacker gets subkeys they can revoke-and-replace, never the primary key itself.
# On the machine holding the full key: export subkeys only
gpg --export-secret-subkeys [email protected] > subkeys-only.asc
# Move subkeys-only.asc to the daily machine, import it there,
# then securely delete the primary secret key from that machine
# (leave it only on the offline backup).
This is genuinely more operational overhead — worth it for a key that
signs software releases or anchors an org's trust; overkill for a
homelab pass store.
8. Key expiration and rotation¶
Extend an expiring key without generating a new one (preserves your existing signatures/trust):
gpg --edit-key [email protected]
gpg> expire
# follow prompts, then:
gpg> save
If a subkey (not the primary) needs rotating, addkey a replacement and
revoke the old one — no need to touch the primary or re-establish trust
with anyone who's signed your key.
9. The automation problem: dedicated keys for specific purposes¶
This is the part that actually matters for something like a backup timer, a CI pipeline, or any script that needs to decrypt something with no human present to type a passphrase.
The core tension: a passphrase protects the private key at rest, but an unattended process has no terminal to enter one at. Your options, roughly ordered from "most convenient, least isolated" to "most secure, most setup":
Option A — a dedicated, passphrase-less key¶
Generate a separate key used for exactly one purpose (e.g. decrypting
entries in a pass store that a backup script reads), with an empty
passphrase:
gpg --full-generate-key
# when prompted for a passphrase, press Enter twice for none
Since there's no passphrase, gpg --decrypt (and by extension pass
show) just works with zero interaction — no agent, no caching, no
timing out.
This only makes sense when:
- The key is scoped to one purpose and nothing else — never your
personal signing/email key.
- It's run as a dedicated, low-privilege system user (matches the
backup user your systemd service already runs as).
- The underlying disk is encrypted (LUKS) or otherwise physically
secured — a passphrase-less key is only as safe as filesystem
permissions and disk access, since anyone who can read the key file
can use it outright.
- Its blast radius is understood and acceptable: if this key leaks, an
attacker can decrypt whatever it protects (e.g. that one pass
store), but nothing else, and it's trivially revocable/replaceable
without touching your identity key.
Set it up for the backup user specifically:
sudo -u backup -H gpg --full-generate-key
# empty passphrase, dedicated UID like "db-backup automation <[email protected]>"
Then point pass init (as that user) at this key's fingerprint, and
make sure the backup user's pass store is separate from your
personal one — otherwise a compromise here exposes everything, not just
the DB credentials it was scoped to protect.
Option B — separate GNUPGHOME per purpose¶
Even with a normal (passphrase-protected or passphrase-less) key, it's
worth keeping automation keys in their own keyring rather than mixed
into your personal ~/.gnupg. GNUPGHOME lets you point GPG at an
entirely separate directory:
export GNUPGHOME=/etc/db-backup/gnupg
gpg --full-generate-key
Any gpg/pass invocation with that env var set operates on a
completely isolated keyring — your personal keys aren't even present to
be accidentally used or leaked. This composes well with Option A: a
passphrase-less key living in its own GNUPGHOME, owned and readable
only by the backup user.
For the systemd service from your earlier setup, this means setting the environment in the unit itself:
[Service]
Environment=GNUPGHOME=/etc/db-backup/gnupg
User=backup
...
Option C — gpg-agent with a long cache TTL¶
If you'd rather keep a passphrase on the key but tolerate some caching,
gpg-agent can hold the unlocked key in memory:
# ~/.gnupg/gpg-agent.conf
default-cache-ttl 86400
max-cache-ttl 86400
You unlock it once (manually, interactively) and it stays cached until the TTL lapses or the agent restarts (which happens on reboot). This is strictly worse for unattended automation than Option A — a reboot silently breaks the next scheduled run until someone manually re-enters the passphrase — but it's a reasonable middle ground if you want some passphrase protection for a key that's mostly used interactively but occasionally triggered by a script.
Option D — hardware-backed keys (YubiKey / smartcard)¶
The primary key's private material never leaves the hardware token; all signing/decryption operations happen on the device itself:
gpg --card-status # detects a connected token
gpg --edit-key [email protected]
gpg> keytocard # move a subkey onto the card
Excellent for a personal identity key you use interactively. Not suitable for unattended automation — a headless script can't satisfy a physical touch/PIN prompt on a hardware token, so this is really an Option A/B alternative only when a human is present at the point of use.
Which one for your setup¶
Given the backup system user, systemd timer, and a pass store
scoped to DB credentials: Option A + Option B together is the
practical answer — a dedicated passphrase-less key, in its own
GNUPGHOME, owned by the backup user, backing a pass store that
holds nothing but those DB credentials. That keeps the blast radius of
a compromise limited to exactly what the backup script needs to touch,
and keeps it fully separate from your personal pass/GPG identity.
10. Troubleshooting¶
"gpg: decryption failed: No secret key" — the key that encrypted
this isn't in your keyring, or you're pointed at the wrong GNUPGHOME.
Check echo $GNUPGHOME and gpg --list-secret-keys.
Hangs with no prompt (common in scripts/systemd) — pinentry is
trying to open a terminal or graphical prompt that doesn't exist in that
context. Either the key needs no passphrase (Option A), or you need
pinentry-mode loopback with a passphrase supplied via file descriptor
(generally a worse trade-off than just removing the passphrase for a
scoped automation key, since it just relocates the plaintext elsewhere).
"gpg: signing failed: Inappropriate ioctl for device" — GPG can't find a TTY for pinentry. Fix for interactive shell use:
export GPG_TTY=$(tty)
Doesn't apply to non-interactive systemd contexts, where the real fix is not needing a prompt at all.
Agent seems stuck/stale after a config change:
gpgconf --kill gpg-agent
It restarts automatically on next use.
11. Case study: how Snowden used GPG to leak the NSA files¶
This is the moment PGP went from "cryptographer's tool" to "thing that took down a mass surveillance program," and it's a genuinely good illustration of GPG used exactly as intended, under real threat-model pressure.
The setup. In early 2013, someone using the name "Citizenfour" emailed documentary filmmaker Laura Poitras. He wouldn't say who he was or what he had — only that she needed to publish her PGP public key so he could encrypt anything he sent her. This wasn't optional friction: it was the whole point. An unencrypted email from a still-anonymous NSA contractor, sitting on a mail server, is trivially interceptable by the very agency he was about to expose. Poitras published her key. He encrypted a document to it. That single exchange — public key out, ciphertext back — is the entire mechanism this guide has been walking through since §4.
The famous friction point. Snowden also wanted journalist Glenn Greenwald involved, and tried for weeks to get him set up with PGP first — sending step-by-step instructions, even recording a "for absolute beginners" tutorial video. Greenwald, by his own later account, kept putting it off; the reporting almost didn't happen because of exactly the usability wall this guide's §9 is built to route around. It only moved forward once documentary producer Micah Lee (an actual security engineer, at the EFF at the time) got directly involved and did the key setup and fingerprint verification for him. The lesson embedded in journalism history here: strong crypto is only as good as someone actually being willing to use it correctly — which is exactly why scoped, low-friction key setups (§9) matter as much as strong key generation.
Fingerprint verification, done properly. This is the part most retellings skip, and it's the part that actually matters. Anyone can publish a PGP key claiming to be "Edward Snowden" or "Laura Poitras" — the public key file itself proves nothing about identity. What Poitras, Greenwald, and later Barton Gellman (Washington Post) did was verify fingerprints out of band: reading the 40-character fingerprint aloud over a separate channel (voice, video, in person) from the one the key file arrived on, so a man-in-the-middle attacker would have to compromise both channels simultaneously to substitute a fake key. This is precisely the practice called out in §1 ("out-of-band fingerprint verification") — it's not a nice-to-have, it's the step that turns "I have a public key" into "I have verified this specific person's public key."
Beyond GPG itself. For the parts of the story that get the most retelling — the Hong Kong hotel meetings, the air-gapped laptops, the famous Rubik's Cube used as a physical identification signal — those weren't GPG at all, they were operational security layered on top of it: encrypting message content solves confidentiality, but doesn't hide metadata (who's talking to whom, when, how often), physical location, or device compromise. Snowden reportedly worked from an air-gapped machine for the most sensitive material and used Tails OS (a live, amnesic Linux distribution built around exactly this threat model) for parts of the operation. The throughline for this whole guide: GPG solves one specific problem — confidentiality and authenticity of a message's content — and real operational security is GPG plus a stack of other deliberate choices around it, not GPG alone.
Doing the same thing yourself¶
None of this is exotic — it's the same mechanics from earlier in this guide, just applied with the seriousness the threat model deserves. If you (or a source you're advising) need to talk to a journalist securely:
- Generate a key dedicated to this correspondence (§3, §9 Option A/B) — not your everyday identity key. If this conversation is ever exposed, you don't want it retroactively linkable to your other GPG activity.
- Get the journalist's public key from a channel they control and publish widely — most major outlets and reporters who handle sensitive sources publish PGP fingerprints on their staff bio page or via SecureDrop (see below), specifically so sources can verify independently rather than trusting whatever key showed up in an email.
- Verify the fingerprint out of band, the way Poitras and Gellman did — compare it over a different channel than the one the key arrived on. A key attached to the same email it's meant to secure proves nothing.
- Encrypt the message body, but know that metadata still leaks. Subject lines, sender/recipient addresses, timestamps, and message size are visible to anyone who can see the traffic, even with the body encrypted. This is exactly why serious source-protection setups don't rely on encrypted email alone.
- For real anonymity, look at SecureDrop, the open-source system (built in part by Aaron Swartz, now maintained by Freedom of the Press Foundation) that most major outlets run specifically for this purpose. It routes submissions through Tor, strips metadata by design, and is a considerably stronger setup than "send a PGP- encrypted email" for anyone with a serious threat model.
- Consider whether GPG is even the right tool today. This is worth
saying plainly: since 2013, the security community's consensus has
shifted somewhat. PGP has real, known weaknesses for this use case —
the 2018 EFAIL vulnerabilities showed practical ways to exfiltrate
plaintext from PGP-encrypted email in common clients, and metadata
exposure (point 4) is structural, not a bug. For most modern
source-journalist communication, tools like Signal (which encrypts
metadata far more thoroughly, and is what many outlets now list
alongside or instead of a PGP key) are often the better-regarded
choice. GPG remains excellent for what this whole guide covers —
signing, file encryption,
pass, verifying release artifacts — but knowing when a differently-shaped tool fits the threat model better is itself part of doing this properly.
12. Quick reference¶
| Task | Command |
|---|---|
| Generate a key | gpg --full-generate-key |
| List secret keys | gpg --list-secret-keys --keyid-format long |
| Export public key | gpg --armor --export [email protected] |
| Import a key | gpg --import file.asc |
| Encrypt for someone | gpg --encrypt --recipient [email protected] file |
| Decrypt | gpg --decrypt file.gpg |
| Detached sign | gpg --detached-sign file |
| Verify | gpg --verify file.sig file |
| Backup private key | gpg --export-secret-keys --armor [email protected] |
| Generate revocation cert | gpg --gen-revoke [email protected] |
| Edit key (subkeys, expiry) | gpg --edit-key [email protected] |
| Use an alternate keyring | GNUPGHOME=/path gpg ... |
| Kill/restart the agent | gpgconf --kill gpg-agent |