Andrew Mercer
on this page

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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