cheat sheet
gpg
Practical GnuPG cheat sheet — generate keys, sign and verify files, encrypt for a recipient, sign git commits and tags, and manage trust without the bureaucracy.
gpg — OpenPGP Encryption & Signing
What it is
gpg is the command-line interface to GnuPG (GNU Privacy Guard), the free implementation of the OpenPGP standard (RFC 4880) maintained by Werner Koch and the GnuPG team since 1997. It provides public-key cryptography for files, email, and git — generating keypairs, signing artefacts, verifying signatures, and encrypting/decrypting data with strong asymmetric and symmetric ciphers. Reach for gpg when you need detached signatures (release tarballs, git commits), email encryption (PGP/MIME), or password-store backends; for modern file-only encryption with smaller keys, age is a simpler alternative.
Install
GnuPG is preinstalled on most Linux distributions and macOS via Homebrew. The current development line is 2.5.x (2.5.19 was released April 2026 and introduces post-quantum ML-KEM support); 2.4.x went unmaintained two months after 2.5.19 landed, so plan on moving to 2.5+ for new keys. Legacy gpg1 (1.4) remains for verifying very old signatures but should not be used for new key generation.
# Debian/Ubuntu
sudo apt install gnupg
# RHEL/Fedora
sudo dnf install gnupg2
# macOS
brew install gnupg
# Verify
gpg --version
Output:
gpg (GnuPG) 2.5.19
libgcrypt 1.11.0
Copyright (C) 2026 g10 Code GmbH
Home: /home/alice/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA, KYBER
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, ...
AEAD: EAX, OCB
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
Syntax
GPG dispatches by long flags rather than subcommands. Output goes to stdout by default unless redirected with -o; --armor switches binary output to base64-armoured text for transport over email or pasted into a web form.
gpg [OPTIONS] --<action> [ARGS...]
gpg --gen-key # interactive
gpg --encrypt -r ALICE FILE
gpg --decrypt FILE.gpg
gpg --sign FILE
gpg --verify FILE.sig FILE
Output: (none — exits 0 on success)
Essential options
| Flag | Meaning |
|---|---|
-a / --armor | ASCII-armoured (base64) output instead of binary |
-o FILE / --output FILE | Write to FILE instead of stdout |
-r USER / --recipient USER | Encrypt for this public key |
-u USER / --local-user USER | Sign with this secret key |
-s / --sign | Make a signature |
-b / --detach-sign | Make a detached signature (separate .sig) |
--clearsign | Sign a text file, leaving the body human-readable |
-e / --encrypt | Encrypt for one or more -r recipients |
-c / --symmetric | Symmetric encryption (passphrase, no keypair) |
-d / --decrypt | Decrypt (also verifies if signed) |
--verify | Verify a signature only |
--list-keys / -k | List public keys |
--list-secret-keys / -K | List secret keys |
--fingerprint | Show key fingerprints |
--export / --export-secret-keys | Print key to stdout |
--import | Import a key from stdin or file |
--edit-key USER | Open the interactive key editor |
--delete-key / --delete-secret-key | Remove a key from the keyring |
The GnuPG home directory
GnuPG stores keys, the trust database, and the agent socket in ~/.gnupg/ (overridable with GNUPGHOME or --homedir). Treat this directory as sensitive — back it up before any destructive operation.
ls -la ~/.gnupg/
GNUPGHOME=/tmp/gpgtest gpg --gen-key # isolated keyring for testing
# Back up the entire home dir before key edits
tar czf ~/gnupg-backup-$(date +%F).tar.gz -C ~ .gnupg
Output:
drwx------ 5 alice alice 4096 May 24 14:00 .
drwx------ 30 alice alice 4096 May 24 14:00 ..
-rw------- 1 alice alice 1280 May 24 14:00 gpg.conf
-rw------- 1 alice alice 9100 May 24 14:00 pubring.kbx
drwx------ 2 alice alice 4096 May 24 14:00 private-keys-v1.d
drwx------ 2 alice alice 4096 May 24 14:00 openpgp-revocs.d
-rw------- 1 alice alice 1600 May 24 14:00 trustdb.gpg
Generate a keypair
--full-generate-key walks through every option — algorithm, key size, expiry, identity. For most users the safer-by-default --quick-gen-key produces a modern Ed25519 signing key with an ECDH encryption subkey in one line. Always set a real expiry (1–2 years) — you can extend it later, but a key with no expiry can never be retired cleanly if you lose the secret.
# Interactive — pick algorithm, size, expiry, identity
gpg --full-generate-key
# One-shot: modern ed25519 keypair, 2-year expiry
gpg --quick-gen-key "Alice Dev <alice@example.com>" ed25519 default 2y
# Batch (unattended) — useful in scripts
cat > keyparams.conf <<EOF
%echo Generating Alice Dev key
Key-Type: EDDSA
Key-Curve: ed25519
Subkey-Type: ECDH
Subkey-Curve: cv25519
Name-Real: Alice Dev
Name-Email: alice@example.com
Expire-Date: 2y
Passphrase: change-me
%commit
%echo Done
EOF
gpg --batch --gen-key keyparams.conf
Output:
gpg: key 9F1B2C3D4E5F6789 marked as ultimately trusted
gpg: revocation certificate stored as '/home/alice/.gnupg/openpgp-revocs.d/A1B2...rev'
public and secret key created and signed.
pub ed25519 2026-05-24 [SC] [expires: 2028-05-24]
A1B2C3D4E5F6789012345678901234567890ABCD
uid Alice Dev <alice@example.com>
sub cv25519 2026-05-24 [E] [expires: 2028-05-24]
List, fingerprint, and identify keys
Keys are referenced by fingerprint (40 hex chars), long ID (last 16), short ID (last 8 — avoid; collisions exist), or user ID substring. Always prefer fingerprints for anything important.
gpg --list-keys # all public keys
gpg --list-keys alice@example.com # by user ID substring
gpg --list-secret-keys # secret keys you hold
gpg --fingerprint alice@example.com # show fingerprint
gpg --list-keys --keyid-format=long # show 16-char long IDs
# Machine-readable
gpg --list-keys --with-colons | awk -F: '/^pub/ {print $5}'
Output:
/home/alice/.gnupg/pubring.kbx
------------------------------
pub ed25519 2026-05-24 [SC] [expires: 2028-05-24]
A1B2 C3D4 E5F6 7890 1234 5678 9012 3456 7890 ABCD
uid [ultimate] Alice Dev <alice@example.com>
sub cv25519 2026-05-24 [E] [expires: 2028-05-24]
Export and import keys
--export writes the public key; --export-secret-keys writes the secret half (treat this output as highly sensitive). -a produces ASCII-armoured output that's safe to paste into emails or commits.
# Export public key
gpg --export -a alice@example.com > alice.pub.asc
# Export secret key (BACK UP THIS FILE, then store it offline)
gpg --export-secret-keys -a alice@example.com > alice.sec.asc
# Export revocation certificate (run this immediately after key gen)
gpg --gen-revoke -a alice@example.com > alice.revoke.asc
# Import a key
gpg --import bob.pub.asc
# Import from URL
curl -s https://example.com/release.pub.asc | gpg --import
Output:
gpg: key B0B1B2B3B4B5B6B7: public key "Bob Smith <bob@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Sign a file
A signature proves the signer held the corresponding secret key at the time of signing. There are three flavours: inline (the original --sign, wraps the data in an OpenPGP packet), clear-signed (text remains readable), and detached (signature lives in a separate .sig / .asc file — the canonical form for release artefacts).
# Inline (binary OpenPGP message containing the original)
gpg --sign report.txt # produces report.txt.gpg
# Clear-signed text — body is still readable
gpg --clearsign release-notes.md # produces release-notes.md.asc
# Detached binary signature
gpg --detach-sign archive.tar.gz # produces archive.tar.gz.sig
# Detached ASCII signature (typical for tarballs distributed on the web)
gpg --detach-sign --armor archive.tar.gz # produces archive.tar.gz.asc
# Sign with a specific key
gpg -u alice@example.com -ab archive.tar.gz
Output:
$ ls archive.tar.gz*
archive.tar.gz
archive.tar.gz.asc
Verify a signature
--verify checks a signature against the original file. For detached signatures, pass both files; GPG prints the signer's identity and a Good signature or BAD signature verdict.
# Detached
gpg --verify archive.tar.gz.asc archive.tar.gz
# Inline / clearsigned (extract & verify in one go)
gpg --decrypt release-notes.md.asc # prints body, exits 0 if valid
# Just the verify step on an inline file
gpg --verify report.txt.gpg
Output:
gpg: Signature made Sun May 24 14:00:00 2026 UTC
gpg: using EDDSA key A1B2C3D4E5F6789012345678901234567890ABCD
gpg: Good signature from "Alice Dev <alice@example.com>" [ultimate]
# BAD signature output:
gpg: BAD signature from "Alice Dev <alice@example.com>" [ultimate]
The exit code is 0 on a good signature and non-zero otherwise — fine to use in scripts.
if gpg --verify --quiet archive.tar.gz.asc archive.tar.gz 2>/dev/null; then
echo "signature OK"
else
echo "signature FAILED"; exit 1
fi
Output: (none — exits 0 on success)
Encrypt for a recipient
--encrypt -r ID encrypts a file so that only the holder of the recipient's secret key can decrypt it. Multiple -r flags add multiple recipients (each gets a copy of the session key). Combine with --sign to make the message both confidential and authenticated.
# Encrypt for Bob; only Bob can decrypt
gpg --encrypt -r bob@example.com message.txt
# Encrypt + sign (recommended) — Bob can verify it really came from Alice
gpg --encrypt --sign -r bob@example.com -u alice@example.com message.txt
# Multiple recipients (yourself + Bob, so you can read it later)
gpg -e -r bob@example.com -r alice@example.com message.txt
# ASCII-armoured for pasting into email
gpg -ea -r bob@example.com message.txt # produces message.txt.asc
Output:
$ ls message.txt*
message.txt
message.txt.gpg # binary form
message.txt.asc # ASCII-armoured form
Decrypt
--decrypt (or just -d, or passing the encrypted file as input) prints the plaintext to stdout. Use -o to write to a file. If the message was signed, GPG verifies the signature automatically and reports it alongside the decryption.
# Decrypt to stdout
gpg --decrypt message.txt.gpg
# Decrypt to a file
gpg -o message.txt -d message.txt.gpg
# Decrypt a signed-and-encrypted file (verifies sig too)
gpg -d secrets.gpg
Output:
gpg: encrypted with 256-bit ECDH key, ID 0123456789ABCDEF, created 2026-05-24
"Alice Dev <alice@example.com>"
gpg: Signature made Sun May 24 14:00:00 2026 UTC
gpg: using EDDSA key A1B2C3D4E5F6789012345678901234567890ABCD
gpg: Good signature from "Alice Dev <alice@example.com>" [ultimate]
<plaintext body here>
Symmetric (passphrase-only) encryption
-c / --symmetric skips the recipient model entirely — the file is encrypted with a passphrase you type interactively. Good for "send a single file to one person over an untrusted channel"; bad for multi-recipient or long-term storage.
gpg -c archive.tar # produces archive.tar.gpg
gpg -ca archive.tar # ASCII-armoured
# Choose the cipher (AES256 is the modern default; spell it out for old GPG)
gpg --cipher-algo AES256 -c archive.tar
# Non-interactive (for scripts) — read passphrase from file
gpg --batch --passphrase-file pw.txt -c archive.tar
Output:
$ ls archive.tar*
archive.tar
archive.tar.gpg
Edit a key — expiry, subkeys, UIDs
--edit-key opens an interactive sub-shell where you can extend expiry, add or revoke subkeys, add new user IDs, change the passphrase, and trust other keys. Type help at the prompt to see all sub-commands.
gpg --edit-key alice@example.com
Output:
gpg> expire # extend the primary key's expiry
gpg> key 1 # select subkey 1
gpg> expire # extend it too
gpg> adduid # add an additional name/email
gpg> passwd # change the passphrase
gpg> trust # set trust level (1..5)
gpg> save # commit and exit
# Non-interactive: extend expiry by 2 years
gpg --quick-set-expire ALICE_FPR 2y
# Revoke a key (after losing the secret, use the saved revocation cert)
gpg --import alice.revoke.asc
Output:
gpg: key A1B2C3D4E5F6789012345678901234567890ABCD: "Alice Dev <alice@example.com>" revoked
Trust model
GPG marks each known key with a trust level. The keys you generated yourself are ultimate; everything else starts unknown until you certify it. The default "web of trust" model only validates a key if it's been signed by enough keys you trust.
| Level | Meaning |
|---|---|
unknown | No information |
never | Don't trust this key's signatures on other keys |
marginal | Trust signatures partially (n marginal = 1 full) |
full | Trust this key to certify others |
ultimate | Same as full, used for your own keys |
# Sign someone's key after verifying their fingerprint in person/by video
gpg --sign-key bob@example.com
# Set trust without signing (private decision)
gpg --edit-key bob@example.com
gpg> trust
gpg> 4 # I trust fully
gpg> save
# Disable trust checks entirely (TOFU model — Trust On First Use)
gpg --trust-model=tofu --verify file.asc file
Output:
gpg: 3 marginal(s) needed, 1 complete(s) needed, classic trust model
gpg: depth: 0 valid: 1 signed: 1 trust: 0-, 0q, 0n, 0m, 0f, 1u
Keyservers
Public keyservers distribute keys by ID. Modern best practice points to keys.openpgp.org (validates email ownership before publishing) rather than the old SKS pool.
# Set default keyserver (in ~/.gnupg/gpg.conf)
echo 'keyserver hkps://keys.openpgp.org' >> ~/.gnupg/gpg.conf
# Publish your public key
gpg --send-keys A1B2C3D4E5F6789012345678901234567890ABCD
# Search for a key
gpg --search-keys alice@example.com
# Fetch a known key by fingerprint
gpg --recv-keys A1B2C3D4E5F6789012345678901234567890ABCD
# Refresh all local keys (catch revocations)
gpg --refresh-keys
Output:
gpg: sending key A1B2C3D4E5F6789012345678901234567890ABCD to hkps://keys.openpgp.org
gpg: key A1B2C3D4E5F6789012345678901234567890ABCD: public key "Alice Dev <alice@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Sign git commits and tags
Git can shell out to gpg to sign commits, tags, and merges; GitHub, GitLab, and Gitea display a "Verified" badge for valid signatures whose key you've uploaded to your profile.
# 1. Tell git which key to use
gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey A1B2C3D4E5F67890
# 2. Sign every commit automatically
git config --global commit.gpgsign true
git config --global tag.gpgsign true
# 3. (macOS only) tell gpg to use a TTY-aware pinentry
echo "use-agent" >> ~/.gnupg/gpg.conf
echo "pinentry-program $(brew --prefix)/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
# 4. Make a signed commit / tag explicitly
git commit -S -m "Signed commit"
git tag -s v1.0 -m "Release 1.0"
# Inspect signatures on the log
git log --show-signature
git tag -v v1.0
Output:
commit a1b2c3d4 (HEAD -> main)
gpg: Signature made Sun May 24 14:00:00 2026 UTC
gpg: using EDDSA key A1B2C3D4E5F6789012345678901234567890ABCD
gpg: Good signature from "Alice Dev <alice@example.com>" [ultimate]
Author: Alice Dev <alice@example.com>
Date: Sun May 24 14:00:00 2026 +0000
Signed commit
Upload gpg --armor --export alice@example.com to your Git host's "GPG keys" settings page so signatures verify on the web UI.
gpg-agent and pinentry
gpg-agent caches your passphrase between invocations so you don't retype it constantly, and delegates passphrase prompts to a pinentry-* program (curses, GTK, Qt, or mac). Tune the cache time in ~/.gnupg/gpg-agent.conf (see Configuration below).
# Reload agent after changing config
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
# Status
gpg-connect-agent 'keyinfo --list' /bye
Output:
S KEYINFO A1B2C3D4... D - - P - - -
OK
Configuration
GnuPG reads three plain-text config files under ~/.gnupg/ (or $GNUPGHOME). Each is loaded by a different component, so options must go in the right file or they're silently ignored. Settings apply on next invocation for gpg.conf, after gpgconf --reload gpg-agent for gpg-agent.conf, and after gpgconf --reload dirmngr for dirmngr.conf.
| File | Read by | Purpose |
|---|---|---|
~/.gnupg/gpg.conf | gpg | Default flags, algorithm preferences, keyserver, output formatting |
~/.gnupg/gpg-agent.conf | gpg-agent | Passphrase cache TTL, pinentry binary, SSH support |
~/.gnupg/dirmngr.conf | dirmngr | Keyserver, HKP/HKPS options, Tor routing, CRL fetching |
gpg.conf — sensible defaults
These defaults prefer modern algorithms, suppress noisy headers, and avoid leaking the short-ID footgun.
# ~/.gnupg/gpg.conf
keyid-format 0xlong
with-fingerprint
no-emit-version
no-comments
personal-cipher-preferences AES256 AES192 AES
personal-digest-preferences SHA512 SHA384 SHA256
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-cipher-algo AES256
s2k-digest-algo SHA512
s2k-mode 3
s2k-count 65011712
keyserver hkps://keys.openpgp.org
# GnuPG 2.5+: opt in to OCB AEAD for symmetric encryption
use-ocb-sym
gpg-agent.conf — cache and pinentry
# ~/.gnupg/gpg-agent.conf
default-cache-ttl 3600 # 1 hour after last use
max-cache-ttl 28800 # 8 hours absolute max
pinentry-program /usr/bin/pinentry-curses
# Uncomment to make gpg-agent serve SSH keys too:
# enable-ssh-support
dirmngr.conf — keyserver and network
dirmngr is the daemon that actually talks to keyservers and downloads CRLs; gpg --send-keys and gpg --recv-keys are thin clients that hand the request to it. Override the keyserver here (or in gpg.conf) and tunnel over Tor for metadata privacy.
# ~/.gnupg/dirmngr.conf
keyserver hkps://keys.openpgp.org
hkp-cacert /etc/ssl/certs/ca-certificates.crt
# Route all keyserver traffic through Tor (requires tor running on 9050):
# use-tor
# Tune timeouts for flaky networks:
connect-timeout 15
http-timeout 30
# Reload daemons after edits
gpgconf --reload gpg-agent
gpgconf --reload dirmngr
# Show everything gpgconf knows about
gpgconf --list-options gpg | head
gpgconf --check-options gpg-agent
Output: (none — exits 0 on success)
The 2026 OpenPGP landscape: LibrePGP vs RFC 9580
OpenPGP's standardisation has split. After years of stalemate, the IETF published RFC 9580 ("OpenPGP", July 2024) defining a v6 key format with mandatory AEAD (authenticated encryption) and obsoleting RFC 4880. GnuPG's maintainer Werner Koch declined to implement v6 and instead drives LibrePGP — a competing successor based on the older crypto-refresh draft, defining its own incompatible v5 key format. Proton, Sequoia-PGP, and the IETF back RFC 9580; GnuPG ships LibrePGP. The two ecosystems generate keys that the other side cannot fully consume, so most deployments still use the v4 format from RFC 4880 to maximise interoperability.
# Inspect a key's packet version (v4 / v5 / v6)
gpg --list-packets alice.pub.asc | grep -E 'version|public key packet'
Output:
:public key packet:
version 4, algo 22, created 1716566400, expires 0
Post-quantum cryptography is landing
GnuPG 2.5.19 (April 2026) was the first stable release with post-quantum encryption — specifically ML-KEM (Kyber, FIPS-203) as a key-encapsulation mechanism, exposed as new Pubkey: KYBER and used in composite (classical + PQ) subkeys following the draft-ietf-openpgp-pqc IETF draft. The 2.4 series went unmaintained two months after 2.5.19 shipped. Sequoia-PGP gained PQ support in late 2025 (also via composite ML-KEM subkeys), so cross-implementation interop for PQ messages is now possible but still draft-stage.
# GnuPG 2.5+: generate a key with a post-quantum encryption subkey
gpg --full-generate-key --expert # pick "(13) ECC + Kyber" or similar
Output: (none — opens interactive prompt)
Sequoia-PGP and sq as a modern alternative
Sequoia-PGP is a Rust reimplementation of OpenPGP led by ex-GnuPG developers. Its CLI, sq, is a clean redesign with git-style subcommands (sq key generate, sq encrypt, sq sign) and uses RFC 9580 v6 keys by default. Two related projects let you opt out of GnuPG without losing tooling compatibility:
sqv— a tiny signature-verifier; Debian'saptuses it by default to verify archive signatures on most architectures.sequoia-chameleon-gnupg(Debian packagegpg-from-sq) — a drop-in replacement for thegpgbinary that speaks the GnuPG CLI but runs on Sequoia underneath. Installing it diverts the realgpgtogpg-g10code.
# Try sq alongside gpg (Debian/Ubuntu)
sudo apt install sq sqv
sq key generate --userid "Alice Dev <alice@example.com>" --output alice-sq.key
sq inspect alice-sq.key
# Drop-in replacement for /usr/bin/gpg
sudo apt install gpg-from-sq
gpg --version # now reports Sequoia's gpg-sq
Output:
gpg-sq (sequoia-chameleon-gnupg 0.x.y) ...
A reimplementation of the gpg interface using Sequoia.
Reach for sq (or age) for new greenfield deployments, but keep gpg for anything that needs OpenPGP v4 interoperability — email plugins, legacy release pipelines, and the long tail of distros that haven't migrated yet.
Common pitfalls
- Short IDs collide. Never reference a key by an 8-char short ID; collisions have been demonstrated. Use the full fingerprint, or at minimum the 16-char long ID (
--keyid-format=long). - No revocation certificate stored. Generate it the moment you create a keypair (
--gen-revoke) and stash it offline — without it, a lost secret key can never be repudiated. - Encrypting for only the recipient. If you encrypt a file with
-r bob@example.comand don't add-r yourself, you can't read it later. Add yourself as a second recipient for archival files. --symmetric+ bad passphrase. GnuPG won't tell you the passphrase is wrong, just that it can't decrypt. Test the passphrase by decrypting immediately after encryption.pinentryhangs in CI. Set--batch --pinentry-mode loopbackand pass--passphraseor--passphrase-file. Otherwise GPG waits forever for a TTY that doesn't exist.- macOS commits show "unverified". Install
pinentry-macvia Homebrew and pointgpg-agent.confat it, or commits will fail silently because the GUI passphrase prompt can't show. - Old DSA/1024-bit keys. Anything generated before 2018 with DSA-1024 or RSA-1024 is obsolete. Migrate to Ed25519 / RSA-4096 and revoke the old key.
--allow-secret-key-importis gone in 2.x. Just use--import.
Real-world recipes
Produce and verify a detached signature for a release
The most common open-source release flow — ship the artefact plus a .asc signature, document the signing key's fingerprint on the project page.
# Sign
gpg --detach-sign --armor -u alice@example.com release-1.0.tar.gz
sha256sum release-1.0.tar.gz > release-1.0.tar.gz.sha256
# What the consumer runs
gpg --recv-keys A1B2C3D4E5F6789012345678901234567890ABCD
gpg --verify release-1.0.tar.gz.asc release-1.0.tar.gz
sha256sum -c release-1.0.tar.gz.sha256
Output:
gpg: Good signature from "Alice Dev <alice@example.com>" [ultimate]
release-1.0.tar.gz: OK
Encrypt a backup directory for offsite storage
Tar + GPG produces a single encrypted blob you can ship to any storage backend.
tar czf - /home/alice/photos \
| gpg --encrypt -r alice@example.com -o photos-$(date +%F).tar.gz.gpg
# Restore later
gpg -d photos-2026-05-24.tar.gz.gpg | tar xzf - -C /tmp/restore
Output:
$ ls -lh photos-2026-05-24.tar.gz.gpg
-rw------- 1 alice alice 1.4G May 24 14:00 photos-2026-05-24.tar.gz.gpg
Verify a downloaded tarball without trusting any global keyring
Use a per-project GNUPGHOME so the project's signing key doesn't pollute your personal trust db.
PROJECT_HOME=$(mktemp -d)
GNUPGHOME=$PROJECT_HOME gpg --import upstream-release.pub.asc
GNUPGHOME=$PROJECT_HOME gpg --verify release-1.0.tar.gz.asc release-1.0.tar.gz
rm -rf "$PROJECT_HOME"
Output:
gpg: key A1B2...ABCD: public key "Upstream Release <release@example.org>" imported
gpg: Good signature from "Upstream Release <release@example.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
Sign apt repositories
Most distros verify package indexes with GPG. To host an apt repo you sign the Release file and publish the public key for clients to install in /etc/apt/trusted.gpg.d/.
# Sign the Release file (creates Release.gpg detached + InRelease inline)
gpg --default-key alice@example.com -abs -o Release.gpg Release
gpg --default-key alice@example.com --clearsign -o InRelease Release
# Publish the signer's public key
gpg --export -a alice@example.com > /var/www/repo/keyring.asc
Output:
$ ls Release*
Release InRelease Release.gpg
Encrypt a one-off file for a recipient you just met
You have their public key on a keyserver but no signed trust path yet. --trust-model=always skips the trust check for this one operation.
gpg --recv-keys 1234567890ABCDEF
gpg --trust-model=always -e -r 1234567890ABCDEF message.txt
Output:
$ ls message.txt*
message.txt
message.txt.gpg
Re-encrypt every file in a directory after rotating recipients
When someone leaves the team you re-issue the key list. Loop over each .gpg file, decrypt, re-encrypt with the new recipient set.
NEW_RECIPS=("-r alice@example.com" "-r charlie@example.com")
for f in *.gpg; do
base="${f%.gpg}"
gpg -d "$f" | gpg -e ${NEW_RECIPS[@]} -o "$base.new.gpg"
mv "$base.new.gpg" "$f"
done
Output:
$ ls *.gpg | wc -l
12
CI: verify a release artefact with no interactivity
Useful inside Docker build steps or GitHub Actions. The --batch --pinentry-mode loopback combination silences passphrase prompts; the key here has no passphrase since it only verifies, never signs.
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
curl -fsSL https://example.com/release.pub.asc | gpg --batch --import
gpg --batch --verify release-1.0.tar.gz.asc release-1.0.tar.gz
Output:
gpg: Good signature from "Upstream Release <release@example.org>"
Rotate (extend) an expired key without losing the identity
When your primary key reaches its expiry, extend it before key consumers fall off the trust path.
gpg --quick-set-expire A1B2C3D4E5F6789012345678901234567890ABCD 2y
# Extend each subkey too
for sub in $(gpg --list-keys --with-subkey-fingerprints alice@example.com \
| awk '/^fpr:::::::::[A-F0-9]+:/' RS=':' | sed -n 2~1p); do
gpg --quick-set-expire A1B2C3D4E5F6789012345678901234567890ABCD 2y "$sub"
done
# Republish
gpg --send-keys A1B2C3D4E5F6789012345678901234567890ABCD
Output:
gpg: sending key A1B2C3D4E5F6789012345678901234567890ABCD to hkps://keys.openpgp.org
Add
--always-trustto encryption commands only when you've manually verified the recipient out-of-band (phone call, in-person fingerprint check). Bypassing the trust db should be a deliberate choice, not the default.
For modern file-only encryption between machines you control,
age(github.com/FiloSottile/age) is dramatically simpler — small keys, no web of trust, one binary. Usegpgwhen you need OpenPGP interoperability (email, package signing, git).
Sources
- GnuPG 2.5.19 release announcement — post-quantum encryption
- draft-ietf-openpgp-pqc — Post-Quantum Cryptography in OpenPGP (IETF)
- RFC 9580 — OpenPGP (IETF, July 2024)
- OpenPGP updates, LibrePGP and RFC 9580 — DidiSoft
- Sequoia-PGP — homepage and project overview
- Post-Quantum Cryptography in Sequoia PGP (Nov 2025)
- sq feature comparison with gpg — Sequoia blog
- Sequoia PGP, sq, gpg-from-sq, v6 OpenPGP, and Debian — DebConf 24
- OpenPGP/Sequoia — Debian Wiki
- gpg-from-sq package — Debian sid