← Back to DevBytes

GnuPG: Setup, Configuration, and Best Practices

What is GnuPG?

GnuPG (GNU Privacy Guard) is a free, open-source implementation of the OpenPGP standard defined by RFC 4880. It provides cryptographic privacy and authentication for data communication, enabling users to encrypt messages, verify identities, and create digital signatures. GnuPG is the de facto standard for secure email, package signing, and encrypted backups in the open-source ecosystem.

At its core, GnuPG operates on a hybrid cryptosystem: it uses public-key cryptography for key exchange and symmetric-key cryptography for bulk data encryption. This gives you the speed of symmetric ciphers combined with the convenience of public-key distribution.

Why GnuPG Matters for Developers

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

As a developer, GnuPG touches your workflow in more ways than you might realize:

Installation

GnuPG is available on virtually every operating system. The modern suite comes in two flavors: gpg (the classic binary) and gpg2 (the modular version from the 2.x series). Today, gpg typically aliases to gpg2. Always prefer the 2.x series for current cryptographic standards and hardware token support.

Linux

# Debian/Ubuntu
sudo apt update && sudo apt install gnupg2 gnupg-agent pinentry-curses

# Fedora/RHEL
sudo dnf install gnupg2 pinentry

# Arch
sudo pacman -S gnupg pinentry

macOS

# Using Homebrew
brew install gnupg pinentry-mac

# After installation, tell GPG to use the macOS pinentry
echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
# Or for Apple Silicon Homebrew paths:
echo "pinentry-program /opt/homebrew/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf

Windows

Download Gpg4win from https://gpg4win.org. This bundle includes GnuPG, Kleopatra (a graphical key manager), and Outlook integration. For command-line-only usage, the minimal installer suffices.

Verify Your Installation

gpg --version
# Expected output: gpg (GnuPG) 2.x.x
# Check that the agent is available:
gpg-agent --version

Key Generation: The Right Way

Modern GnuPG (2.1+) supports Elliptic Curve Cryptography (ECC), which offers equivalent security to RSA with much smaller keys and faster operations. Unless you need compatibility with legacy systems that only support RSA, always generate an ECC key.

Generating a Modern ECC Keypair

# Generate a full-featured ECC key with ed25519 primary + encryption subkey
gpg --quick-generate-key "Your Full Name <you@example.com>" ed25519 cert 2y

# The prompt will ask for a passphrase. Use a strong one.
# Breakdown:
#   ed25519  - elliptic curve algorithm (modern, fast, secure)
#   cert     - key usage flag for the primary key (certification only)
#   2y       - expiration of 2 years (you can extend later)

Adding an Encryption Subkey

The command above creates a primary key for certification and signing. For encryption, you need a dedicated subkey. This separation of key material is critical for security — your primary key should only be used to certify other keys and subkeys, never for daily operations.

# Add a Curve25519 encryption subkey
gpg --quick-add-key <KEYID> cv25519 encr 2y

# To find your KEYID, run:
gpg --list-keys --keyid-format long
# Look for the line starting with 'pub' and copy the full key ID after the algorithm

Adding an Authentication Subkey (for SSH)

# You can even use your GPG key for SSH authentication!
gpg --quick-add-key <KEYID> ed25519 auth 2y

# Later, use gpg-agent as an SSH agent:
gpg --export-ssh-key <KEYID> > ~/.ssh/id_ed25519_gpg.pub
# Add this public key to your remote ~/.ssh/authorized_keys

Legacy RSA Key (if you must)

# Only use this if you need compatibility with pre-2.1 GPG clients
gpg --full-generate-key
# Select: (1) RSA and RSA (default)
# Keysize: 4096
# Expiration: 2y

Key Management Essentials

Listing Keys

# List all public keys in your keyring
gpg --list-keys

# List keys with full fingerprints and key IDs
gpg --list-keys --keyid-format long --fingerprint

# List secret keys (your private key material)
gpg --list-secret-keys

# Show a specific key in detail
gpg --list-key <KEYID>

Exporting and Sharing Your Public Key

# Export ASCII-armored public key (ready for email, websites, keyservers)
gpg --export --armor <KEYID> > my-public-key.asc

# Export binary format (smaller, for local use)
gpg --export <KEYID> > my-public-key.gpg

Importing Others' Keys

# Import from a file
gpg --import recipient-public-key.asc

# Import directly from a keyserver
gpg --keyserver hkps://keys.openpgp.org --search-keys recipient@example.org

# After importing, verify the key fingerprint with the owner
gpg --fingerprint recipient@example.org

Establishing Trust

Importing a key doesn't mean you trust it. You must explicitly assign trust after verifying the fingerprint through an out-of-band channel (video call, in-person meeting, secure messaging).

# Edit the key to set trust
gpg --edit-key recipient@example.org

# Inside the GPG edit prompt:
trust
# Select: 5 (I trust ultimately) — only for your own keys or fully verified keys
# Select: 4 (I trust fully)    — for keys you've verified in person
# Select: 3 (I trust marginally) — for keys verified via web of trust
save

Key Expiration and Renewal

Keys should always have an expiration date. An expired key can be renewed, but a lost key without expiration is a permanent liability.

# Change expiration date on the primary key
gpg --edit-key <KEYID>
expire
# Enter new expiration: 2y, 1y, 6m, etc.
save

# After renewing, re-export and re-distribute your public key
gpg --export --armor <KEYID> > my-public-key-updated.asc

Daily Operations: Encryption and Signing

Symmetric Encryption (Password-Based)

For personal files or backups, symmetric encryption is simple and doesn't require key management. GnuPG uses a strong cipher with key derivation from your passphrase.

# Encrypt a file with a passphrase (symmetric, using AES256 by default)
gpg --symmetric --cipher-algo AES256 secret-file.txt

# Decrypt it
gpg --decrypt secret-file.txt.gpg > secret-file.txt
# You'll be prompted for the passphrase

# For batch/scripted use (careful with passphrase storage):
gpg --batch --passphrase "your-strong-passphrase" --symmetric secret-file.txt

Public-Key Encryption

# Encrypt a file for a specific recipient
gpg --encrypt --armor --recipient recipient@example.org message.txt
# Outputs message.txt.asc (ASCII-armored)

# Encrypt for multiple recipients
gpg --encrypt --armor --recipient alice@example.org --recipient bob@example.org file.txt

# Encrypt a binary file (omit --armor for smaller output)
gpg --encrypt --recipient recipient@example.org large-data.bin

# Decrypt: GPG automatically selects the correct private key
gpg --decrypt message.txt.asc > message.txt

Signing Documents

# Create a detached signature (signature in separate file)
gpg --detach-sign --armor document.txt
# Creates document.txt.asc — distribute both files

# Verify a detached signature
gpg --verify document.txt.asc document.txt

# Create a clear-signed document (signature embedded in readable text)
gpg --clearsign document.txt
# Outputs document.txt.asc with the message + signature inline

# Verify a clear-signed document (no filename needed)
gpg --verify document.txt.asc

# Sign AND encrypt in one operation
gpg --sign --encrypt --recipient recipient@example.org document.txt

Signing Git Commits and Tags

# Configure git to use your GPG key
git config --global user.signingkey <KEYID>
git config --global commit.gpgsign true

# Sign a specific commit
git commit -S -m "Release v2.0 with security fixes"

# Sign a tag
git tag -s v2.0.0 -m "Signed release tag"

# Verify commits and tags
git log --show-signature
git tag -v v2.0.0

# Tell GitHub/GitLab about your public key:
gpg --export --armor <KEYID>
# Copy the output and paste into GitHub Settings → SSH and GPG keys → New GPG key

Configuration Deep Dive

GnuPG's behavior is governed by configuration files in ~/.gnupg/. Understanding these files gives you fine-grained control over security defaults, key server behavior, and agent settings.

The Main Configuration: ~/.gnupg/gpg.conf

Here is a well-annotated, security-conscious configuration that reflects current best practices:

# ~/.gnupg/gpg.conf — Recommended hardened configuration

# --- Algorithm preferences (strong ciphers first) ---
# Personal cipher preferences for recipients who don't specify
personal-cipher-preferences AES256 AES192 AES128
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
personal-compress-preferences BZIP2 ZLIB ZIP Uncompressed

# Default key preferences when generating new keys
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES128 BZIP2 ZLIB ZIP Uncompressed

# --- Keyserver configuration ---
# Use HKPS (encrypted keyserver connection)
keyserver hkps://keys.openpgp.org
# Alternative: hkps://keyserver.ubuntu.com

# Do not automatically retrieve keys from keyservers (prevents tracking)
no-auto-key-retrieve

# Refresh keys automatically but only over HKPS
auto-key-retrieve

# --- Security hardening ---
# Require strong digests for certifications
cert-digest-algo SHA512

# Force use of the agent (modern behavior)
use-agent

# Always show full key fingerprints for verification
with-fingerprint
with-key-origin

# Display verbose information
verbose

# Always use .gpg extension for encrypted files
# Leave this commented unless you want consistent naming
# --no-armor

# --- Trust model ---
# Use the web of trust + TOFU (Trust On First Use)
trust-model tofu+pgp

# --- Compliance ---
# Enforce strong signature digests
require-cross-certification

# Disable insecure algorithms
# These are already disabled by default in modern GPG, but being explicit doesn't hurt
disable-cipher-algo IDEA
disable-cipher-algo 3DES
disable-cipher-algo BLOWFISH
# SHA1 is still allowed for some legacy interop, but you can disable it:
# personal-digest-preferences SHA512 SHA384 SHA256
# cert-digest-algo SHA512

GPG Agent Configuration: ~/.gnupg/gpg-agent.conf

The agent manages your private keys, caches passphrases, and can even act as an SSH agent. A well-configured agent improves both security and usability.

# ~/.gnupg/gpg-agent.conf

# Pinentry program (for passphrase prompts)
# macOS:
pinentry-program /opt/homebrew/bin/pinentry-mac
# Linux (graphical):
# pinentry-program /usr/bin/pinentry-gnome3
# Linux (terminal/SSH):
# pinentry-program /usr/bin/pinentry-curses

# Cache passphrases for 1 hour (36000 seconds)
# Set to 0 for no caching — prompts every time
default-cache-ttl 36000

# Maximum cache TTL (even with repeated use)
max-cache-ttl 72000

# Enable SSH agent support
enable-ssh-support

# Write the SSH agent socket
# This allows using GPG as a drop-in SSH agent replacement
write-env-file ~/.gnupg/gpg-agent.env

Applying Configuration Changes

# After editing gpg-agent.conf, reload the agent
gpg-connect-agent reloadagent /bye

# Or kill and restart (more thorough)
gpgconf --kill gpg-agent
gpg-agent --daemon

# Verify the agent is using the new configuration
gpgconf --list-dirs agent-socket

Securing Your Private Key: The Master Key Strategy

Your primary (master) key is your cryptographic identity. If it's stolen, an attacker can impersonate you indefinitely — or at least until the key expires. The best practice is to keep your master key offline, stored on a dedicated air-gapped machine or hardware token, and use subkeys for daily operations.

Step 1: Generate the Master Key Offline

On a dedicated, air-gapped machine (a Raspberry Pi with no network, or a laptop booted from a live USB):

# Generate the master key with a strong passphrase
gpg --full-generate-key
# Choose: ECC (ed25519) or RSA 4096
# Set expiration: 5y
# Use a strong, unique passphrase — store it in a password manager

Step 2: Create Subkeys for Daily Use

# On the same air-gapped machine
KEYID=<your-master-key-id>

# Encryption subkey
gpg --quick-add-key $KEYID cv25519 encr 2y

# Signing subkey
gpg --quick-add-key $KEYID ed25519 sign 2y

# Authentication subkey (for SSH)
gpg --quick-add-key $KEYID ed25519 auth 2y

Step 3: Export Subkeys Only to Your Daily Machine

# Export the subkeys WITHOUT the master key
gpg --export-secret-subkeys $KEYID > subkeys.gpg

# Also export the public key
gpg --export --armor $KEYID > public-key.asc

# Transfer these files to your daily machine via a trusted medium
# (USB stick with verified checksums, QR code, etc.)

# On your daily machine, import the subkeys
gpg --import subkeys.gpg
gpg --import public-key.asc

Step 4: Remove the Master Key from Daily Machine

After importing subkeys, the master key is still present in the keyring but marked as "secret but not usable" since the secret material isn't there. Verify this:

# On your daily machine, check key status
gpg --list-secret-keys
# The master key should show a '#' after the key ID, e.g.:
# sec#  ed25519 2024-01-01 [C] [expires: 2029-01-01]
#     KEYID...       [C] = certification only
# ssb   cv25519 2024-01-01 [E] [expires: 2026-01-01]
# ssb   ed25519 2024-01-01 [S] [expires: 2026-01-01]
# ssb   ed25519 2024-01-01 [A] [expires: 2026-01-01]

# The '#' after 'sec' confirms the master secret key is absent
# The '>' after 'ssb' confirms the subkey secret is present

Hardware Tokens: YubiKey and OpenPGP Cards

For the strongest security, move your subkeys onto a hardware token like a YubiKey (5 series), Nitrokey, or any OpenPGP-compatible smartcard. The private key material never leaves the device — all cryptographic operations happen on the chip.

Moving Subkeys to a YubiKey

# First, ensure your YubiKey supports OpenPGP (YubiKey 5 series)
# Configure the YubiKey's OpenPGP applet
gpg --card-status

# If the card is empty or needs reset:
gpg --card-edit
admin
factory-reset

# Move your signing subkey to the YubiKey
gpg --edit-key <KEYID>
# Select the signing subkey:
key 1
keytocard
# Choose: (1) Signature key

# Move your encryption subkey
key 1   # deselect signing
key 2   # select encryption
keytocard
# Choose: (2) Encryption key

# Move your authentication subkey
key 2   # deselect encryption
key 3   # select authentication
keytocard
# Choose: (3) Authentication key

save

Using the YubiKey Daily

# After moving keys to the card, your keyring will show stubs:
gpg --list-secret-keys
# Keys will show '>' indicating stub pointers to the smartcard

# Operations now require the YubiKey inserted:
gpg --sign document.txt
# The YubiKey's LED will flash; you'll need to enter the card PIN

# Configure card PIN and Admin PIN (if not already set)
gpg --card-edit
admin
passwd
# Set PIN (default is 123456) and Admin PIN (default is 12345678)

SSH Authentication via YubiKey

# With gpg-agent configured for SSH support (see gpg-agent.conf above):
# Export the SSH public key from the card
gpg --export-ssh-key <KEYID> > ~/.ssh/id_ed25519_yubikey.pub

# Add this public key to remote servers
ssh-copy-id -i ~/.ssh/id_ed25519_yubikey.pub user@remote-server

# Ensure SSH uses gpg-agent
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

# Now SSH uses the YubiKey for authentication
ssh user@remote-server
# You'll be prompted for the YubiKey PIN (not your GPG passphrase)

Key Revocation: Plan for the Worst

Every key you generate should have a corresponding revocation certificate created immediately — before you even use the key. Store this certificate securely (encrypted, printed on paper, in a safe). If your key is compromised or lost, you publish the revocation certificate to render the key invalid.

Generating a Revocation Certificate

# Generate a pre-made revocation certificate
gpg --gen-revoke --armor <KEYID> > revocation-<KEYID>.asc

# The output looks like an ASCII-armored PGP block.
# Store this securely — anyone with this file can revoke your key.
# Recommendations:
#   - Print it and store in a physical safe
#   - Encrypt it with a strong passphrase and store digitally
#   - Split it using Shamir's Secret Sharing across trusted parties

# To revoke the key (when needed):
gpg --import revocation-<KEYID>.asc
gpg --keyserver hkps://keys.openpgp.org --send-keys <KEYID>

Best Practices: A Checklist

Here is a condensed, actionable list of best practices distilled from years of cryptographic operations experience:

Automated Usage in Scripts and CI/CD

Sometimes you need GPG in non-interactive contexts — CI pipelines, backup scripts, or configuration management. This requires careful handling of passphrases.

Using GPG in Batch Mode

# Decrypt a secret in a CI pipeline
# The passphrase must come from a secure environment variable
gpg --batch --passphrase "$GPG_PASSPHRASE" --decrypt secrets.enc.gpg > secrets.txt

# Symmetric encrypt for backups
tar cz /important/data | \
  gpg --batch --passphrase "$BACKUP_PASSPHRASE" \
      --symmetric --cipher-algo AES256 \
      --output backup-$(date +%Y%m%d).tar.gz.gpg

# For CI, consider using a dedicated keypair without passphrase
# (stored in CI secret variables, used only for CI operations)
# Generate a CI-specific key:
gpg --quick-generate-key "CI Bot <ci@example.org>" ed25519 cert 1y
# Export without passphrase protection (careful!):
# This key should ONLY be used for automated signing and have minimal privileges

Encrypting Secrets with sops (Mozilla)

sops is an excellent tool that wraps GPG for YAML/JSON secret management:

# Install sops
# Linux: curl -LO https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
# macOS: brew install sops

# Create a .sops.yaml configuration
cat > .sops.yaml << 'EOF'
creation_rules:
  - pgp: >
      KEYID1,
      KEYID2
    encrypted_regex: '^(password|secret|token|key|credentials|db_.*)$'
EOF

# Encrypt a secrets file
sops --encrypt secrets.yaml > secrets.enc.yaml

# Decrypt for use
sops --decrypt secrets.enc.yaml > secrets.yaml

Troubleshooting Common Issues

Problem: "gpg: signing failed: No secret key"

# This usually means the agent doesn't have the key or pinentry isn't working
# Check if the key is actually present:
gpg --list-secret-keys --keyid-format long

# Check agent status:
gpg-connect-agent /bye

# If agent isn't running:
gpg-agent --daemon

# If pinentry is missing (common on headless servers):
# Install pinentry-curses and update gpg-agent.conf:
echo "pinentry-program /usr/bin/pinentry-curses" >> ~/.gnupg/gpg-agent.conf
gpg-connect-agent reloadagent /bye

Problem: "gpg: no valid OpenPGP data found"

# This occurs when importing or decrypting non-ASCII-armored data without --armor
# If the file is binary GPG data, use:
gpg --decrypt file.gpg

# If it's ASCII-armored, ensure it has the proper headers:
# -----BEGIN PGP MESSAGE-----
# ...
# -----END PGP MESSAGE-----

Problem: Keyserver operations timeout or fail

# Try an alternative keyserver
gpg --keyserver hkps://keyserver.ubuntu.com --search-keys user@example.org

# Check network connectivity to HKPS port 443
curl -v https://keys.openpgp.org

# If behind a restrictive firewall, export keys manually and share via HTTPS

Conclusion

GnuPG remains one of the most powerful and widely-adopted cryptographic tools available to developers. Its flexibility — from simple file encryption to complex web-of-trust identity verification — makes it indispensable in modern software development workflows. By following the practices outlined in this tutorial — using ECC keys, separating master keys from subkeys, leveraging hardware tokens, and maintaining rigorous key hygiene — you can build a robust cryptographic foundation that protects your code, your communications, and your identity. The initial investment in learning GPG pays dividends in security, trust, and compliance throughout your career. Start by generating a proper ECC keypair today, sign your next commit, and gradually adopt the deeper practices as your comfort grows. The command line is your interface to a global web of trust — use it wisely.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles