A cryptographic signature is one of the few things online that still means exactly what it says. If the key is yours and the signature verifies, the content came from you. Full stop. No vendor handed you this identity, no CA can pull it, no platform can suspend it. It exists because you generated the key, and it stays yours for exactly as long as you hold the private half. Most of what we casually call “online identity” is borrowed: a handle someone can ban, a checkmark someone can strip, an email address a domain owner can take back the day they feel like it. A GPG signature lives outside all of that. The key that signed this paragraph is either yours or it belongs to someone else, and nobody gets a vote.
For years that property was a thing crypto nerds cared about and almost nobody else did. It is turning into a daily concern, and fast. We used to be able to assume that the human behind an email or a commit or a voice note was a human, and probably the one the headers claimed. That assumption is coming apart. AI-generated text, voice, and video keep getting cheaper and more convincing, and the default mental model is sliding toward “could be anyone, could be nobody.” The noise is already in my inbox: convincing PR messages aping a maintainer’s style, synthetic voice calls pretending to be a colleague who needs a favour, whole sites of plausible prose spun up in minutes. Proving that you wrote the post or shipped the commit stops being free. A signature is the one thing in that pile that AI cannot fake without stealing your key first.
There is a second pressure on the same setup, slower and much further out. If a big enough quantum computer ever shows up, everything you ever encrypted with classical public-key crypto becomes readable after the fact. The name for this is harvest-now-decrypt-later, and state-level adversaries are assumed to be doing it already. They grab your encrypted traffic today and sit on it, planning to crack it in ten or twenty years when the hardware exists. You cannot un-send the ciphertext you already leaked, but you can stop adding to the pile, and that means reaching for post-quantum encryption now, while the tooling is still a bit rough.
So here is the question I actually care about: will the GPG setup you have right now still hold up in a decade? Attribution is already biting. Long-horizon confidentiality will bite later. A key you generate today should survive both.
This post is the setup I landed on after chewing on that question. One offline masterkey, three identity aliases bound to it, post-quantum encryption via ML-KEM (Kyber), and three independent backup layers. It runs long, because every choice here is a trade-off and the reasoning is the part worth reading. New to GPG? Go read GPG explained first. From here on I assume you have generated at least one key in your life.
What we are building
[Masterkey - Ed25519 - Certify only] ← offline, in a vault, on paper
│
├── UID 1: Tom Herder <tom@byteherder.com>
├── UID 2: kapott <kapott@pm.me>
├── UID 3: Tom Meurs <tom.meurs@gmail.com>
│
├── [Sub 1 - Ed25519] [S] Sign → email and git
├── [Sub 2 - ML-KEM+X448] [E] Encrypt → quantum-safe
└── [Sub 3 - Ed25519] [A] Auth → SSH login
One root, three identities, three subkeys. Why carve it up like this?
| Choice | Reason |
|---|---|
| Masterkey = certify-only | The master only signs other keys and identities. Never used for mail, never used for git. Keep it offline and your identity survives every laptop theft. |
| Separate subkeys for Sign / Encrypt / Auth | You can rotate or revoke each one independently. Laptop stolen? Revoke subkeys, keep identity. |
| Multiple UIDs on one key | All three aliases are cryptographically bound to the same master - proof they come from the same person. |
| ML-KEM (Kyber) for encryption | Defends against harvest-now-decrypt-later. Someone recording your ciphertext today cannot decrypt it in 2046 with a quantum computer. |
| Ed25519 for signing and auth | Signatures do not have a harvest problem - they are verified at the moment of signing. Full post-quantum signing (ML-DSA) exists in RFC 9580 but tooling support in 2026 is still thin (Thunderbird, GitHub, SSH servers). Ed25519 is the pragmatic choice. |
One trade-off I want to flag before you copy any commands. Putting kapott@pm.me and tom.meurs@gmail.com on the same key makes it publicly provable that “kapott” and “Tom Meurs” are the same human. Great if you want attribution across your aliases. Genuinely dangerous if kapott is supposed to stay pseudonymous, because once that public key hits a keyserver the link is permanent. Keyservers do not forget, and you cannot ask them to. If you need a real pseudonym, give it its own separate key (scenario B at the end).
Prerequisites
You need GnuPG 2.5 or later. ML-KEM subkey generation does not exist in 2.4.
# Arch
sudo pacman -S gnupg paperkey qrencode zbar diceware pwgen veracrypt
# Debian/Ubuntu - enable the official GnuPG repo for 2.5.x
curl -fsSL https://repos.gnupg.com/GPG-KEY-gnupg.asc \
| sudo tee /etc/apt/keyrings/gnupg.asc > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/gnupg.asc] https://repos.gnupg.com/debian bookworm main" \
| sudo tee /etc/apt/sources.list.d/gnupg.list
sudo apt update && sudo apt install -y gnupg2 paperkey qrencode zbar-tools veracrypt diceware pwgen
Verify:
gpg --version | head -2 # must be 2.5.x or later
Generate the keys on a clean OS. An Ubuntu live-USB or Tails is the paranoid option, and paranoia is the right setting here: nothing your daily laptop might be quietly running ever gets near the master. Work inside a dedicated GNUPGHOME so none of this leaks into your existing keyring:
mkdir -p ~/gpg-build && chmod 700 ~/gpg-build
export GNUPGHOME=~/gpg-build
Harden GnuPG
Drop a hardened config in $GNUPGHOME/gpg.conf before generating anything. This disables weak algorithms and opts into modern defaults:
cat > "$GNUPGHOME/gpg.conf" <<'EOF'
keyid-format 0xlong
with-fingerprint
with-subkey-fingerprint
list-options show-uid-validity
no-emit-version
no-comments
no-greeting
export-options export-minimal
personal-cipher-preferences AES256 AES192 AES
personal-digest-preferences SHA512 SHA384 SHA256
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
weak-digest SHA1
disable-cipher-algo 3DES
disable-cipher-algo CAST5
disable-pubkey-algo RSA1024
require-cross-certification
no-symkey-cache
throw-keyids
keyserver hkps://keys.openpgp.org
EOF
chmod 600 "$GNUPGHOME"/gpg.conf
gpgconf --kill gpg-agent && gpgconf --launch gpg-agent
A passphrase you can actually remember
The passphrase is the last thing standing between an attacker and your master once they have a copy of the encrypted file. It has to survive offline brute-forcing by someone with hardware you cannot predict. Use Diceware: a random run of seven or more words pulled from a big list. Easier to keep in your head than %Xh9$!Lq, and stronger at the same time.
diceware -n 8 -d ' ' --no-caps
# schemerig koning fiets bramboes tunnel ijverig mosterd kraaier
Write it on paper and lock that paper somewhere physical. Lose the passphrase and every backup in this post turns into landfill, no exceptions.
Create the masterkey
We want Ed25519, certify-only, ten years.
gpg --expert --full-generate-key
Answers: 11 (ECC with custom capabilities) → toggle s to remove Sign (Certify only remains) → q → 1 (Curve 25519) → 10y → your primary UID (I use my real name + my real mail address). GPG asks for the passphrase twice and you are done.
Capture the fingerprint immediately so the rest of this post can use $KEYID:
export KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/ {print $10; exit}')
echo "$KEYID" > ~/gpg-build/KEY_INFO.txt
Add UIDs for each alias
gpg --expert --edit-key "$KEYID"
Inside the editor:
gpg> adduid # Tom Herder <tom@byteherder.com>
gpg> adduid # kapott <kapott@pm.me>
gpg> uid 1 # pick your primary
gpg> primary
gpg> save
Every UID is now signed by the same master, which is the cryptographic proof that they belong to one person.
Add the subkeys
Still in --edit-key:
Signing subkey (Ed25519): addkey → 10 (ECC sign only) → 1 → 2y.
Encryption subkey - the post-quantum one: addkey → 16 (ECC and Kyber) → 4 (Kyber 1024 + X448) → 2y.
This is the hybrid piece, and it is the whole reason for GnuPG 2.5. The subkey welds ML-KEM-1024 (NIST FIPS 203, quantum-safe) onto X448 (classical ECC). To read your ciphertext an attacker has to break both. So you stay covered whichever side eventually cracks: classical crypto folding to a quantum computer, or some nasty surprise turning up in ML-KEM itself. Belt and braces, on purpose.
Authentication subkey (Ed25519): addkey → 11 (ECC custom) → toggle s off and a on → q → 1 → 2y → save.
Verify with gpg --list-secret-keys --with-subkey-fingerprint. You should see [C], [S], [E], [A] markers lining up with master, sign, encrypt, auth.
While you are still in here, generate a revocation certificate. Think of it as the nuclear button for the day the masterkey is compromised:
gpg --output "$GNUPGHOME/revocation-$KEYID.asc" --gen-revoke "$KEYID"
Take the master offline
Here is the step that makes the rest of this worth the effort. Export everything, wipe the master secret out of the working keyring, then re-import only the subkey secrets.
cd "$GNUPGHOME"
gpg --export-secret-keys --armor --output MASTER-FULL.asc "$KEYID"
gpg --export-secret-subkeys --armor --output SUBKEYS-ONLY.asc "$KEYID"
gpg --export --armor --output PUBLIC.asc "$KEYID"
gpg --export-ownertrust > OWNERTRUST.txt
gpg --delete-secret-keys "$KEYID"
gpg --import SUBKEYS-ONLY.asc
gpg --import-ownertrust OWNERTRUST.txt
Check the result:
gpg --list-secret-keys --with-subkey-fingerprint
# sec# ed25519/0x... [C] ← the # means "master secret not present"
# ssb ed25519/... [S]
# ssb ky1024_cv448/.. [E]
# ssb ed25519/... [A]
That # is what we came for. Your daily laptop now carries nothing but disposable subkeys, so revoking them after a theft costs you a few minutes instead of your identity.
Three backup layers
One backup is no backup. So I run three independent paths, and each one assumes the other two have already failed when I reach for it.
Hot - VeraCrypt volume on multiple clouds. Always reachable, survives a lost laptop.
veracrypt --text --create cloud-backup/gpg-vault.vc \
--volume-type=normal --size=128M \
--encryption=AES-Twofish-Serpent --hash=Whirlpool \
--filesystem=FAT --pim=0 --keyfiles="" \
--random-source=/dev/urandom
sudo veracrypt --text --mount cloud-backup/gpg-vault.vc /tmp/vault --pim=0 --keyfiles=""
sudo cp MASTER-FULL.asc SUBKEYS-ONLY.asc PUBLIC.asc OWNERTRUST.txt \
revocation-$KEYID.asc KEY_INFO.txt /tmp/vault/
sync && sudo veracrypt --text --dismount /tmp/vault
rclone copy cloud-backup/gpg-vault.vc gdrive:SecureBackups/
rclone copy cloud-backup/gpg-vault.vc proton:SecureBackups/
Give the VeraCrypt volume a different passphrase than your GPG key, so one leak does not knock over the other. The AES-Twofish-Serpent cascade with an Argon2-derived key is comfortably post-quantum: Grover’s algorithm halves the effective symmetric key length, and you still walk away with roughly 128 bits of margin.
Cold - paperkey + QR codes. Survives cloud outage and account bans.
Paperkey strips the redundant structure out of a secret key and keeps only the bytes that actually matter, which is exactly what you want on archival paper. It handles Ed25519 cleanly. It cannot do Kyber yet, since that is too new and there is no compact encoding for it, so the master goes through paperkey while the full armored export goes through QR codes split across several PNGs with high error correction.
# Paperkey for the master
paperkey --secret-key <(gpg --export-secret-keys "$KEYID") \
--output master.paperkey.txt
# QR stack for the full bundle
split -b 1500 -d -a 2 MASTER-FULL.asc part_
i=1; total=$(ls part_* | wc -l)
for f in part_*; do
qrencode -l H -s 10 -m 4 \
-o "qr-$(printf '%02d' $i)-of-$(printf '%02d' $total).png" < "$f"
i=$((i+1))
done
Test your restore before you trust it:
for f in qr-*.png; do zbarimg --raw --quiet "$f" >> combined.asc; done
diff combined.asc MASTER-FULL.asc && echo "restore works"
Print on archival paper with a laser printer, because inkjet ink fades inside a decade and the whole point is to outlast that. Stick the stack in a fire-resistant box, or split it across two locations if you are feeling thorough.
Hardware - YubiKey (optional). This covers the ugly case where your backups leak but the physical token is still in your pocket. In 2026 YubiKey firmware still does not do PQC subkeys, so only the Ed25519 sign and auth subkeys go on the token while the Kyber encrypt subkey stays in software. Yubico’s roadmap pencils in PQC for firmware 6 in 2027, so this gap should close.
Per-employer identities
Client and employer work gives you two scenarios, and the choice between them is one you should make on purpose rather than by accident.
Scenario A - add a UID to the existing masterkey. Easy, and it cryptographically proves the same person stands behind all your identities. The catch is that every colleague who imports your employer public key also gets a full list of your personal aliases. Fine for tom@byteherder.com, which is my public identity anyway. A real problem for kapott@pm.me.
Scenario B - separate key per employer. One more key to babysit, but the separation is clean. You can still cross-sign it with your main master when you want the attribution, and if you do not, the identities stay cryptographically unrelated. This is my default whenever privacy-per-context actually matters.
export GNUPGHOME=~/gpg-dsm
mkdir -p "$GNUPGHOME" && chmod 700 "$GNUPGHOME"
gpg --expert --full-generate-key # repeat the §6–§8 flow
Keep a separate VeraCrypt volume per identity (dsm-vault.vc, clientX-vault.vc). Same three-layer backup as before, just with the blast radius walled off per client.
Daily use
Publish the public key. Keys.openpgp.org makes you verify each UID by email before it publishes anything, which doubles as a nice sanity check that you typed the addresses right:
gpg --keyserver hkps://keys.openpgp.org --send-keys "$KEYID"
Git signing. Pin your commits to the signing subkey. That trailing ! is what forces GnuPG to use this specific subkey instead of picking one for you:
SIGNKEY=$(gpg --list-secret-keys --with-colons "$KEYID" \
| awk -F: '/^ssb/ && $12~/s/ {print $5; exit}')
git config --global user.signingkey "$SIGNKEY!"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Per-repo overrides let you sign employer commits with the employer key:
git config user.email tom.meurs@dsm.com
git config user.signingkey "DSM_SIGNKEY!"
SSH via gpg-agent. Your auth subkey becomes your SSH key:
gpg --list-secret-keys --with-keygrip "$KEYID" # note the A-subkey keygrip
echo "<KEYGRIP>" >> ~/.gnupg/sshcontrol
# In your shell rc
export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
ssh-add -L # paste this line in GitHub → SSH keys
Email. Once all three addresses point at the same master key, Thunderbird matches the UID to your From: header on its own. Mutt does the same once you set pgp_default_key = $KEYID.
Rotation
Every eighteen months, comfortably before the two-year subkey expiry lands, I bring the master back for a few minutes and shove the expiry out again. It is the same VeraCrypt-mount dance as the backups:
sudo veracrypt --text --mount ~/gpg-build/cloud-backup/gpg-vault.vc /tmp/vault
export GNUPGHOME=$(mktemp -d) && chmod 700 "$GNUPGHOME"
gpg --import /tmp/vault/MASTER-FULL.asc
gpg --expert --edit-key "$KEYID"
# key 1, key 2, key 3 → expire → 2y → save
gpg --export-secret-subkeys --armor "$KEYID" > ~/SUBKEYS-NEW.asc
rm -rf "$GNUPGHOME" && unset GNUPGHOME
sudo veracrypt --text --dismount /tmp/vault
GNUPGHOME=~/.gnupg gpg --import ~/SUBKEYS-NEW.asc
gpg --keyserver hkps://keys.openpgp.org --send-keys "$KEYID"
If a subkey ever leaks, it is the same flow with one extra step: revkey the compromised subkey, export, publish. The master never moved, so your identity is untouched. This is the payoff for all the ritual.
The broader principle
None of this is minimal, and I am not going to pretend otherwise. Seven Diceware words, three backup layers, a recurring ritual, a couple of USB sticks living in different drawers. You could skip the whole thing, sign your commits with some SaaS “identity service,” and never read another word about keyservers.
The catch is that a SaaS identity belongs to the provider, not to you. They can revoke it, they can lose it in an incident, they can be served a subpoena and hand it over, and the day they pivot their business model you migrate on their clock instead of yours. I dug into this whole control-versus-convenience question in Sovereign Infrastructure. Owning the root is not about saving a few euros a month. It is about refusing to depend on a system I cannot open up and inspect.
Attribution is the same argument, except it is already biting today. When text and voice and video can all be faked convincingly on hardware anyone can buy, the honest default for any message becomes “could be anyone.” Signing your commits does not stop someone from churning out garbage in your name. What it does is hand everyone who cares a way to check the difference, and that check holds even when the forgery is otherwise perfect. The nice part is that the anchor only works if it already exists, so the move is to start signing now and have it waiting.
Quantum-safety is that same argument stretched across decades. The thing I am defending against is not a quantum computer cracking my RSA next Tuesday. It is a patient adversary quietly recording my encrypted traffic today and betting on the hardware showing up later. The only counter is to stop feeding them ciphertext that will one day be cheap to crack, which means reaching for post-quantum encryption now, rough tooling and all.
A key I generate today should still hold up in twenty years. That is the bar I set, and everything above is what it took to clear it with commodity tools, out in the open, without handing my trust to anyone but myself.
