GnuPG es la implementación libre del estándar OpenPGP (RFC 4880). La idea central es criptografía de llave pública: generas un par de llaves donde la llave privada nunca sale de tu control, y distribuyes la pública a quien necesite verificar tu identidad o enviarte datos cifrados. Lo que cifras con la pública solo lo descifra la privada; lo que firmas con la privada lo verifica cualquiera que tenga la pública.
El modelo funciona así internamente: GPG no cifra el archivo directamente con tu llave RSA o Ed25519 —eso sería lento e ineficiente para archivos grandes—. En su lugar genera una llave de sesión simétrica aleatoria (AES-256 por defecto en GnuPG 2.x), cifra el contenido con ella, y cifra esa llave de sesión con la llave pública del destinatario. El destinatario descifra la llave de sesión con su privada y luego descifra el contenido. Esto se llama cifrado híbrido.
Necesitas GPG en producción más de lo que crees: cada vez que apt update verifica la firma de un repositorio, hay una llave GPG de por medio. Cuando alguien te manda un backup de base de datos sensible, quieres cifrarlo antes de que toque S3. Cuando firmas un commit de git que va a producción, estableces una cadena de autoría verificable. Si no entiendes cómo funciona esto, cometes errores silenciosos —backups “cifrados” con passphrase vacía, repositorios con confianza allow-unauthenticated, commits firmados con una llave expirada que nadie detectó a tiempo.
El web of trust es el modelo de confianza de OpenPGP: en lugar de una CA centralizada, la confianza se establece porque personas que ya conoces firman la llave pública de alguien, y tú confías en esas personas como introductores. En la práctica, para uso de sysadmin en infraestructura propia, el web of trust importa menos que entender bien la gestión de llaves. Donde sí importa es al verificar software de terceros: si la llave del mantenedor está firmada por cinco personas de la comunidad Debian que ya tienes en tu keyring, eso vale algo.
# ── Generación de par de llaves ──────────────────────────────────────
# En Debian Bookworm, GnuPG 2.2.x es el default. Para scripting
# o servidores sin pinentry-gtk, instala pinentry-tty:
sudo apt install gnupg pinentry-tty
# Edita ~/.gnupg/gpg-agent.conf para forzar el pinentry de terminal:
echo "pinentry-program /usr/bin/pinentry-tty" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent # reinicia el agente para que tome el cambio
# Genera el par de llaves. --full-gen-key da control sobre el algoritmo.
# Para uso nuevo: Ed25519 + Curve25519 (más rápido, llaves más cortas).
gpg --full-gen-key
# Elige: (9) ECC and ECC → (1) Curve 25519 → 2y (expira en 2 años)
# Nombre: Carlos Mendoza / Email: carlos@infra.ejemplo.com
# Passphrase: usa algo fuerte, esto protege la llave privada en disco
# Lista tus llaves con el fingerprint completo:
gpg --list-keys --fingerprint carlos@infra.ejemplo.com
# Salida esperada (fingerprint acortado aquí por legibilidad):
# pub ed25519 2024-01-15 [SC] [expires: 2026-01-15]
# A3F2 91CC 4D18 3B27 F09E 1A4B 5C8D E23F 7612 9A01
# uid [ultimate] Carlos Mendoza <carlos@infra.ejemplo.com>
# sub cv25519 2024-01-15 [E] [expires: 2026-01-15]
# Guarda el fingerprint; lo usaremos como identificador canónico.
FINGERPRINT="A3F291CC4D183B27F09E1A4B5C8DE23F76129A01"
# ── Exportar e importar llaves ───────────────────────────────────────
# Exporta la llave pública (formato ASCII armored, seguro para pegar en
# un README, correo, o servidor de llaves):
gpg --armor --export "$FINGERPRINT" > carlos-mendoza.pub.asc
# Importar la llave pública de otro usuario (ej: tu colega Ana):
gpg --import ana-garcia.pub.asc
# Verifica el fingerprint con Ana por un canal fuera de banda
# (videollamada, en persona) antes de firmarla:
gpg --fingerprint ana@infra.ejemplo.com
# Firma la llave de Ana para establecer confianza local:
gpg --sign-key ana@infra.ejemplo.com
# ── Cifrar un archivo para un destinatario ───────────────────────────
# -e: encrypt -r: recipient -a: ASCII armor (opcional, para texto)
# El archivo resultante solo lo puede descifrar quien tenga la privada de Ana.
gpg -e -r ana@infra.ejemplo.com backup-db-2024-06.tar.gz
# Genera backup-db-2024-06.tar.gz.gpg
# Para cifrar para múltiples destinatarios (tú mismo + Ana):
gpg -e -r "$FINGERPRINT" -r ana@infra.ejemplo.com backup-db-2024-06.tar.gz
# ── Descifrar ────────────────────────────────────────────────────────
# GPG detecta automáticamente para qué llave está cifrado el archivo
# y pide la passphrase de tu llave privada:
gpg -d backup-db-2024-06.tar.gz.gpg > backup-db-2024-06.tar.gz
# ── Firmar archivos y verificar firmas ──────────────────────────────
# Firma detached (la firma va en archivo separado, el original no cambia):
gpg --armor --detach-sign release-1.4.tar.gz
# Genera release-1.4.tar.gz.asc
# Verificar la firma (necesitas tener la llave pública del firmante):
gpg --verify release-1.4.tar.gz.asc release-1.4.tar.gz
# "Good signature from..." → OK
# "BAD signature" → el archivo fue modificado, NO uses ese binario
# ── Verificar integridad de una descarga ─────────────────────────────
# Ejemplo real: verificar la ISO de Debian o un paquete de terceros.
# Primero importa la llave del proyecto (aquí: clave de releases Debian):
gpg --keyserver keyring.debian.org --recv-keys 0x673A03E4C1DB921F
# Descarga el archivo y su firma:
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA512SUMS
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA512SUMS.sign
# Primero verifica la firma sobre el archivo de checksums:
gpg --verify SHA512SUMS.sign SHA512SUMS
# Luego verifica el checksum de la ISO:
sha512sum -c SHA512SUMS --ignore-missing
# ── Git: firmar commits ──────────────────────────────────────────────
# Configura git para usar tu llave:
git config --global user.signingkey "$FINGERPRINT"
git config --global commit.gpgsign true # firma todos los commits por default
git config --global gpg.program gpg
# Firma un commit puntualmente si no tienes gpgsign=true:
git commit -S -m "deploy: actualiza config de nginx para TLS 1.3"
# Verifica firmas en el log:
git log --show-signature -1
# ── APT y la firma de repositorios ──────────────────────────────────
# APT en Debian Bookworm usa signed-by= en sources.list.d.
# Nunca uses apt-key add (deprecado). El flujo correcto:
# 1. Descarga la llave del repositorio de terceros (ejemplo: PostgreSQL):
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| gpg --dearmor \
| sudo tee /usr/share/keyrings/postgresql.gpg > /dev/null
# 2. Referencia la llave en el sources.list:
echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] \
https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list
# APT verificará cada Release file de ese repositorio contra esa llave
# específica. Si la firma falla, apt update lo rechaza con error.
# ── age: alternativa para cifrado de archivos ────────────────────────
# age (https://age-encryption.org) es más simple: sin web of trust,
# sin subllaves, sin gestión de keyrings. Ideal para backups automatizados.
sudo apt install age
# Genera un par de llaves age:
age-keygen -o ~/.config/age/key.txt
# La llave pública aparece en el comentario del archivo, empieza con "age1..."
# Cifra un archivo con la llave pública (puede estar en un archivo o inline):
age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8x \
-o backup.tar.gz.age backup.tar.gz
# Descifra con la llave privada:
age -d -i ~/.config/age/key.txt -o backup.tar.gz backup.tar.gz.age
# Para backups desatendidos en cron, age es más robusto que GPG:
# no requiere gpg-agent corriendo, no hay expiración de llaves,
# y el formato es determinista y fácil de auditar.
Lo que está pasando debajo del capó
La distinción entre las capacidades [SC] y [E] en la salida de --list-keys no es cosmética. S es Sign, C es Certify (firmar otras llaves), E es Encrypt. GPG genera automáticamente una subllave de cifrado (cv25519 [E]) separada de la llave maestra (ed25519 [SC]). Esta separación es deliberada: puedes rotar la subllave de cifrado sin invalidar las firmas que ya hiciste con la llave maestra, y si la subllave se compromete, el daño está contenido.
El FINGERPRINT que usamos como variable a lo largo del ejemplo es el identificador canónico de la llave: 40 caracteres hexadecimales, el hash SHA-1 de la llave pública. Los key IDs cortos de 8 caracteres tienen colisiones conocidas —hay ataques documentados donde se generan llaves maliciosas con el mismo ID corto—. Usa siempre el fingerprint completo cuando lo necesites en scripts o configuración.
En el bloque de --sign-key de Ana, el paso “verificar por canal fuera de banda” no es una formalidad: si importas una llave que alguien te mandó por correo sin verificar el fingerprint en persona o por voz, un atacante con control de ese canal puede haberte hecho importar su llave haciéndose pasar por Ana. GPG no puede detectar eso; el modelo de seguridad asume que la verificación fuera de banda ocurrió.
La parte del cifrado para múltiples destinatarios (-r "$FINGERPRINT" -r ana@...) funciona porque GPG cifra la misma llave de sesión AES una vez para cada destinatario, con la llave pública de cada uno. El archivo resultante contiene múltiples bloques de “llave de sesión cifrada” pero un solo cuerpo cifrado. Incluirte a ti mismo como destinatario es imprescindible si quieres poder descifrar lo que enviaste —de lo contrario generas un archivo que literalmente tú no puedes abrir.
En el bloque de APT, gpg --dearmor convierte el formato ASCII armored (texto) al formato binario que espera signed-by=. La ruta /usr/share/keyrings/ es el lugar correcto para llaves de repositorios de terceros en Debian Bookworm; no uses /etc/apt/trusted.gpg.d/ para repositorios externos porque esas llaves tienen confianza global sobre todos los repositorios, lo que amplía innecesariamente la superficie de ataque.
La diferencia fundamental entre GPG y age en el contexto de backups automatizados: GPG requiere el agente corriendo para operaciones con passphrase, gestiona expiración de llaves, y su formato de keyring puede corromperse. age tiene un formato de llave plano, sin agente, sin expiración, sin base de datos de estado. Para un cron que cifra y sube backups a las 3am, age falla menos silenciosamente y es más fácil de auditar un año después cuando alguien necesita restaurar.
La verificación de la ISO en dos pasos —primero gpg --verify sobre SHA512SUMS, luego sha512sum -c— es el patrón correcto. Si verificas el checksum sin verificar la firma del archivo de checksums, solo estás comprobando integridad contra transmisión, no autenticidad. Un servidor comprometido puede servir una ISO modificada con checksums igualmente modificados; la firma GPG sobre ese archivo de checksums es lo que ancla la cadena de confianza al mantenedor real.