GPG is one of those tools I use constantly but only ever remember half the flags for, which is exactly why I keep coming back to write things like this down properly. GNU Privacy Guard is a full implementation of the OpenPGP standard — public-key encryption, digital signatures, and symmetric encryption, all built around a genuinely elegant trust model once you get past the initial learning curve.
I’ve verified every command in the sections below directly: key generation, listing, symmetric and asymmetric encryption/decryption, signing, verification, and export all ran successfully on a live system.
Public-Key Cryptography, Briefly
GPG is built on asymmetric cryptography: each identity has a key pair — a public key you share freely, and a private key you never share, ever. Anything encrypted with someone’s public key can only be decrypted with their private key. Anything signed with your private key can be verified by anyone using your public key, proving the message came from you and hasn’t been altered.
This solves two distinct problems: confidentiality (encryption — only the intended recipient can read it) and authenticity/integrity (signing — anyone can verify who sent it and that it wasn’t tampered with). GPG also supports symmetric encryption, which uses a single shared passphrase rather than a key pair — simpler, but requires securely sharing that passphrase out-of-band.
Installing GPG
Debian/Ubuntu:
sudo apt install gnupg
RHEL/Fedora:
sudo dnf install gnupg2
Check version:
gpg --version
I confirmed gpg (GnuPG) 2.4.4 on the test system used for verification in this article.
Generating a Key Pair
Interactive generation:
gpg --full-generate-key
You’ll be walked through:
- Key type (RSA and RSA is the default and safe general-purpose choice; ECC/Curve25519 options are also available on modern GPG for smaller, faster keys)
- Key size (3072 or 4096 bits for RSA)
- Expiration (I generally set 1-2 years and plan to renew, rather than “never expire,” since a key with no expiration that’s later compromised has no natural cutoff)
- Name, email, and comment (your User ID)
- A passphrase to protect the private key
For scripted/non-interactive generation (useful for automation, CI pipelines, or testing):
cat > keygen-batch.txt <<'EOF'
%no-protection
Key-Type: RSA
Key-Length: 3072
Subkey-Type: RSA
Subkey-Length: 3072
Name-Real: Your Name
Name-Email: you@example.com
Expire-Date: 1y
%commit
EOF
gpg --batch --generate-key keygen-batch.txt
I ran this exact batch method and confirmed it correctly generates a primary key plus an encryption subkey, complete with a revocation certificate automatically stored under ~/.gnupg/openpgp-revocs.d/.
Listing Keys
gpg --list-keys
gpg --list-secret-keys
Example output format (verified):
pub rsa3072 2026-07-31 [SCEAR] [expires: 2027-07-31]
B4C90F8C1A0D501D1CF06A20E457BD8352F52DD2
uid [ultimate] Test User <test@example.com>
sub rsa3072 2026-07-31 [SEA] [expires: 2027-07-31]
The long string is the key’s full fingerprint — the authoritative identifier for a key, since names and emails can collide or be spoofed. The letters in brackets (SCEAR) indicate key capabilities: Sign, Certify, Encrypt, Authenticate, Restricted.
Encrypting Files (Asymmetric — Public Key)
gpg -r recipient@example.com -e file.txt
-r— recipient (must have their public key in your keyring)-e— encrypt
This produces file.txt.gpg. For ASCII-safe output (useful when pasting into email or a text field rather than sending a binary attachment):
gpg -r recipient@example.com -e -a file.txt
-a/--armor produces a .asc file in Base64 text form instead of raw binary.
I verified this full round trip on a live system: encrypting a test file to a recipient, confirming the .gpg output file exists, then decrypting it back and getting the exact original plaintext.
Decrypting Files
gpg -d -o output.txt file.txt.gpg
-d— decrypt-o— output file (without this, GPG prints decrypted content to stdout)
If the private key has a passphrase, you’ll be prompted (via pinentry in interactive use).
Symmetric Encryption (Passphrase-Based, No Key Pair Needed)
gpg -c file.txt
-c/--symmetric— symmetric encryption using a passphrase you’ll be prompted for
Specify a stronger cipher explicitly (AES256 is a solid modern default):
gpg --symmetric --cipher-algo AES256 file.txt
Decrypt:
gpg -d -o file.txt file.txt.gpg
I verified this exact symmetric encrypt/decrypt round trip using --passphrase in batch mode, confirming the decrypted output exactly matched the original plaintext.
Digital Signatures
Detached Signature (Original File Stays Untouched)
gpg --detach-sign -o file.txt.sig file.txt
Verify:
gpg --verify file.txt.sig file.txt
I confirmed this produces a Good signature from "..." message when verification succeeds, exactly matching documented GPG behavior.
Clear-Signed Text (Human-Readable, Signature Wrapped Around Plaintext)
gpg --clearsign file.txt
Produces file.txt.asc with the original text still readable, wrapped in -----BEGIN PGP SIGNED MESSAGE----- markers.
Combined Sign-and-Encrypt
gpg -r recipient@example.com --sign --encrypt file.txt
This proves both who sent the file (signature) and that only the recipient can read it (encryption) — the combination you generally want for genuinely sensitive communication.
Exporting and Importing Keys
Export your public key to share with others:
gpg --armor --export you@example.com > publickey.asc
Import someone else’s public key:
gpg --import theirkey.asc
Export your private key (handle with extreme care — this is your actual identity):
gpg --armor --export-secret-keys you@example.com > privatekey.asc
Trusting a Key
By default, an imported public key is “unknown” trust — GPG will still let you encrypt to it but will warn you. Sign a key to indicate you’ve personally verified it belongs to who it claims:
gpg --sign-key recipient@example.com
Set trust level explicitly:
gpg --edit-key recipient@example.com
gpg> trust
gpg> 5
gpg> quit
Trust levels range 1 (unknown/don’t know) through 5 (ultimate — reserved for your own keys).
Key Servers
Publish your public key so others can find it:
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
Fetch someone else’s key by ID or email:
gpg --keyserver keyserver.ubuntu.com --search-keys someone@example.com
gpg --keyserver keyserver.ubuntu.com --recv-keys KEY_ID
Note that traditional SKS keyservers have had significant operational and privacy issues in recent years (spam attacks, inability to delete data due to the append-only design); many in the OpenPGP community now favor the WKD (Web Key Directory) approach or keys.openpgp.org, which supports verified deletion and doesn’t publish third-party signatures by default.
Revoking a Key
If a key is compromised or simply retired, publish a revocation certificate (generated automatically at key creation, or generate one now):
gpg --gen-revoke you@example.com > revoke.asc
gpg --import revoke.asc
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
This is exactly why generating a revocation certificate at key creation time (which GPG does automatically) and storing it somewhere safe and separate from the key itself matters — if you lose access to the key but still have the revocation certificate, you can still tell the world not to trust it anymore.
Common Real-World Use Cases
Verifying downloaded software — many projects publish a detached .sig alongside a release tarball:
gpg --keyserver keyserver.ubuntu.com --recv-keys PROJECT_KEY_ID
gpg --verify release.tar.gz.sig release.tar.gz
Encrypting backups before uploading to untrusted storage:
tar czf - /important/data | gpg --symmetric --cipher-algo AES256 -o backup.tar.gz.gpg
Signing Git commits (widely used for supply-chain integrity):
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
The Web of Trust Model in More Depth
GPG’s trust model is genuinely different from the centralized certificate authority model most people are familiar with from HTTPS, and understanding it properly changes how you should think about key verification.
Rather than a small number of authorities everyone trusts by default, GPG’s “web of trust” works through individuals vouching for each other. When you sign someone’s public key, you’re making a public statement: “I have personally verified this key genuinely belongs to this person” — typically done after checking their key fingerprint against something verified out-of-band (in person, over a verified phone call, or through some other channel you trust independently of the key exchange itself).
# Check a key's fingerprint carefully before signing it
gpg --fingerprint recipient@example.com
The fingerprint is what you’d actually read aloud or compare character-by-character with someone in person — never trust a fingerprint that arrived through the same channel as the key itself, since that channel could be compromised or intercepted.
gpg --sign-key recipient@example.com
gpg --send-keys --keyserver keyserver.ubuntu.com THEIR_KEY_ID
This is genuinely more labor-intensive than trusting a centralized CA, which is a large part of why GPG’s web of trust never achieved mainstream adoption the way HTTPS certificate authorities did — but for the specific use case of verifying software releases or communicating with a known, specific set of people, it remains a sound and widely used model, especially in open-source software supply chains.
Subkeys: Why GPG Generates More Than One Key
When you generate a key pair, GPG actually creates a primary key (used for certification — signing other keys, and by default for signing your own data) plus a subkey (used for encryption). This split exists for a genuinely practical reason: if your encryption subkey is ever compromised, you can revoke and replace just that subkey while keeping your primary key’s identity, existing signatures, and web-of-trust relationships intact.
gpg --list-keys --with-subkey-fingerprints recipient@example.com
Add an additional subkey (useful for rotating encryption capability periodically without disturbing your primary identity):
gpg --edit-key you@example.com
gpg> addkey
gpg> save
Revoke a specific subkey without touching the primary key:
gpg --edit-key you@example.com
gpg> key 1
gpg> revkey
gpg> save
This subkey architecture is also exactly what makes it practical to keep your primary private key on an offline, air-gapped machine (or a hardware token) while only exposing a subkey on internet-connected devices for day-to-day encryption/signing — a genuinely strong security posture for anyone whose GPG identity matters a great deal (software release signing keys being the clearest example).
Using GPG With a Hardware Security Token
For higher-security use cases, GPG supports storing private key material on a hardware token (like a YubiKey) rather than as a file on disk at all — meaning the private key material never actually leaves the token, and every signing/decryption operation happens on the device itself.
gpg --card-status
Moving an existing subkey onto a card:
gpg --edit-key you@example.com
gpg> key 1
gpg> keytocard
Once moved, GPG on any machine with the token plugged in can perform operations using that key, but the actual private key material is never extractable from the token — meaningfully raising the bar against key theft compared to a passphrase-protected file on disk, since even a fully compromised machine can’t exfiltrate the key itself, only potentially misuse it while the token is physically present and unlocked.
Batch and Scripted Operations for Automation
Beyond the interactive workflows shown earlier, real automation (CI/CD pipelines signing release artifacts, automated encrypted backup scripts) needs fully non-interactive operation:
# Encrypt without any interactive prompts, trusting the recipient key without manual confirmation
gpg --batch --yes --trust-model always -r ci-release-key@example.com -e artifact.tar.gz
# Sign in batch mode using a key with no passphrase (appropriate only for automation-dedicated keys,
# never for a personal identity key)
gpg --batch --yes --pinentry-mode loopback --passphrase-fd 3 3<<< "$SIGNING_PASSPHRASE" \
--detach-sign -o artifact.tar.gz.sig artifact.tar.gz
I verified the --batch --yes --trust-model always pattern directly — it correctly performs the encryption without any interactive prompt, which is exactly the behavior needed inside a non-interactive pipeline. The --passphrase-fd approach (passing the passphrase through a file descriptor rather than a command-line argument) is the safer pattern for automation specifically because command-line arguments are visible to other processes on the same system via /proc/PID/cmdline, while a file descriptor is not.
Comparing GPG to Other Encryption Approaches
| Tool/approach | Model | Typical use case |
|---|---|---|
| GPG | Asymmetric (web of trust) + symmetric | Email encryption, software signing, file/backup encryption |
| age | Asymmetric, deliberately minimal | Modern alternative to GPG for simple file encryption, much smaller feature surface |
| OpenSSL (raw) | Asymmetric/symmetric primitives, no key management layer | Building blocks for custom applications, not typically used directly for personal file encryption |
| LUKS | Symmetric, whole-disk | Full-disk encryption, not applicable to per-file encryption at all |
| S/MIME | Asymmetric, CA-based (not web of trust) | Email encryption in enterprise environments with existing PKI |
age in particular has gained real traction as a deliberately simpler alternative for cases where GPG’s full feature set (subkeys, web of trust, multiple algorithms) is more complexity than the task actually needs — worth knowing about if you find yourself reaching for GPG purely for basic file encryption and finding the ceremony heavier than the task warrants.
Troubleshooting
“gpg: no valid OpenPGP data found” — the input file isn’t actually a valid GPG-encrypted/signed file, or it’s been corrupted/truncated in transit.
“gpg: decryption failed: No secret key” — you’re trying to decrypt something encrypted to a key you don’t have the private half of; confirm with gpg --list-secret-keys which keys you actually hold.
Passphrase prompt not appearing (batch/script context) — GPG’s pinentry mechanism expects a terminal or a properly configured agent; for scripting, use --batch --passphrase (accepting the security tradeoff of a passphrase potentially appearing in shell history/process list) or better, --passphrase-fd to pass it via a file descriptor instead.
“gpg: WARNING: unsafe permissions on homedir” — fix ownership/permissions on your GPG directory:
chmod 700 ~/.gnupg
Security Best Practices
- Always set an expiration date on keys and plan to renew or rotate rather than using “never expires.”
- Store your revocation certificate somewhere separate from your private key, so a lost/stolen key can still be revoked.
- Prefer
AES256explicitly for symmetric operations rather than relying on older cipher defaults on very old GPG versions. - Never share your private key; if you suspect it’s been exposed, revoke it immediately rather than waiting.
- Use a strong, unique passphrase on your private key — an unencrypted private key on disk is a single-file compromise away from full identity theft in the cryptographic sense.
Summary
GPG provides encryption, digital signatures, and symmetric encryption built on the OpenPGP standard, all commonly used for secure communication, software integrity verification, and backup encryption. The commands that matter most day to day are --gen-key/--full-generate-key for setup, -e/-d for asymmetric encryption and decryption, -c for symmetric encryption, and --sign/--verify for authenticity — all of which I’ve confirmed behave exactly as documented on a live system.