A cryptographic signature is one of the few things online that means exactly what it says. If the key is yours and the signature verifies, the content came from you. No vendor issued this identity, no CA can revoke it, no platform can suspend it. It exists because you generated the key, and it stays yours as long as you control the private half. Most of what we call “online identity” is on loan from someone else: a handle that can be banned, a checkmark that can be removed, an email address that a domain owner can reclaim. A GPG signature sits outside all of that. Either the key that signed this paragraph is yours, or it is not, and no one else gets to decide.
That property used to be a niche concern. It is becoming a daily one. It used to be reasonable to assume that the person behind an email, a commit, or a voice note was actually a person, and probably the one the headers claimed. That assumption is dissolving. AI-generated text, voice, and video keep getting cheaper and better, and the default is drifting toward “this could be anyone, or nobody.” The noise is already here: fake PR messages in a maintainer’s style, synthetic voice calls pretending to be a colleague, entire sites of believable prose written in minutes. Proving that you wrote the post, shipped the commit, or said the words stops being automatic. A cryptographic signature is the anchor that AI cannot forge without the key.
A second, slower pressure sits on the same setup, on a much longer timeline. If a sufficiently large quantum computer ever shows up, every message you ever encrypted with classical public-key crypto becomes readable, retroactively. This has a name: harvest-now-decrypt-later. State-level adversaries are assumed to be doing it right now. Encrypted traffic they grab today, they plan to decrypt in ten or twenty years. The defence is to stop handing them classical ciphertext, which means post-quantum encryption today, while the tooling is still maturing.
The real question is whether your current GPG setup will still be defensible in a decade: for daily attribution, which is already biting, and for long-horizon confidentiality, which will.
This post walks through a setup that tries to do both. One offline masterkey, three identity aliases bound to it, post-quantum encryption via ML-KEM (Kyber), and three independent backup layers. It is long because every choice is a trade-off and the reasoning matters as much as the commands. If you are brand new to GPG, start with GPG explained first. This post assumes you have made at least one key.
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 this shape?
| 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. |
There is one trade-off worth naming up front. 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 person. That is a feature if you want attribution. It is a serious bug if kapott is meant to stay pseudonymous. Once the public key is on a keyserver you cannot undo the link - keyservers do not forget. If you need a real pseudonym, use a fully 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 keys on a clean OS. Ubuntu live-USB or Tails is the paranoid default - no malware from your daily driver touches your master. Work in a dedicated GNUPGHOME so nothing 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 line of defence when someone has a copy of your encrypted master. It needs to survive offline brute-forcing, including attackers with future hardware. Use Diceware - a random sequence of seven or more words from a large list. Easier to remember than %Xh9$!Lq, and stronger.
diceware -n 8 -d ' ' --no-caps
# schemerig koning fiets bramboes tunnel ijverig mosterd kraaier
Write it on paper. Lock the paper somewhere physical. If you lose the passphrase, every backup in this post becomes landfill.
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. Cryptographic proof that they belong together.
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. The subkey combines ML-KEM-1024 (NIST FIPS 203, quantum-safe) with X448 (classical ECC). An attacker needs to break both to read your ciphertext. You are covered if either side fails - classical crypto falling to a quantum computer, or an unexpected weakness in ML-KEM itself.
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 here, generate a revocation certificate. This is the nuclear button if the masterkey is ever compromised:
gpg --output "$GNUPGHOME/revocation-$KEYID.asc" --gen-revoke "$KEYID"
Take the master offline
This is the step that makes the whole thing worth doing. We export everything, wipe the master secret from the working keyring, and 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 the whole point. Your daily laptop now has only disposable subkeys. Revoking them does not cost you your identity.
Three backup layers
One backup is no backup. Three independent backup paths, each assuming the others have failed:
Hot - VeraCrypt volume on multiple clouds. Always reachable, survives laptop loss.
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/
Use a different passphrase for the VeraCrypt volume than for your GPG key. Failure should not cascade. AES-Twofish-Serpent cascade with an Argon2-derived key is post-quantum fine - Grover halves effective symmetric key length, and you still have ~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 leaves only the actual secret bytes - perfect for printing on archival paper. It handles Ed25519 cleanly but not Kyber (too new, no compact encoding yet), so the master goes through paperkey and the full armored export goes through QR codes split across multiple 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 (inkjet fades in a decade). Put the stack in a fire-resistant box or split it across two locations.
Hardware - YubiKey (optional). Covers the case where your backups leak but the hardware is intact. In 2026 YubiKey firmware does not yet support PQC subkeys, so only the Ed25519 sign and auth subkeys go on the token, and the Kyber encrypt subkey stays in software. Yubico’s roadmap has PQC for firmware 6 in 2027.
Per-employer identities
For client or employer contexts there are two scenarios. Pick deliberately.
Scenario A - add a UID to the existing masterkey. Easy, and cryptographically proves continuity across identities. The downside: every colleague who imports your employer public key also sees your personal aliases. That is fine for tom@byteherder.com (my public identity), and a problem for kapott@pm.me.
Scenario B - separate key per employer. One extra key to manage, but clean separation. You can still cross-sign it with your main master if you want attribution; otherwise the identities stay cryptographically unrelated. This is the default for anything where privacy-per-context 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 (dsm-vault.vc, clientX-vault.vc) per identity. Same three-layer backup, isolated blast radius.
Daily use
Publish the public key. Keys.openpgp.org verifies each UID by email before publishing - a nice sanity check:
gpg --keyserver hkps://keys.openpgp.org --send-keys "$KEYID"
Git signing. Pin commits to your signing subkey (the trailing ! forces that specific subkey):
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. Thunderbird matches UID to From: automatically once all three addresses point at the same master key. Mutt picks up pgp_default_key = $KEYID the same way.
Rotation
Every eighteen months, before the two-year subkey expiry hits, bring the master back temporarily and push the expiry out. Same VeraCrypt-mount dance:
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: same flow, but revkey the compromised subkey, export, publish. Your identity is untouched. That is why we went through all of this.
The broader principle
This setup is not minimalist. Seven Diceware words, three backup layers, a ritual every eighteen months, two USB sticks. You could sign your git commits with a SaaS “identity service” and never touch any of this.
But a SaaS identity is not yours. The provider owns it. They can revoke it, they can lose it, they can be compelled to hand it over, and when they change their business model you migrate on their timeline. As I explored in Sovereign Infrastructure, the point of owning the root is not saving money - it is refusing to depend on systems you cannot inspect or control.
Attribution is the same argument, and it is the one that already bites. In a world where text, voice, and video can be synthesised convincingly on commodity hardware, the default assumption about any message is drifting toward “could be anyone, could be nobody.” Signing your commits and your words does not stop anyone from faking content in your name, but it gives anyone who cares a cryptographic way to tell the difference. The signature is the anchor that AI cannot forge without the key. Start signing now and the anchor is already there when you need it.
Quantum-safety is the same argument extended across decades. The threat model is not “a quantum computer breaks my RSA tomorrow.” It is “someone patient is recording my encrypted traffic today and waiting.” The only defence is to stop giving them the ciphertext that will eventually be cheap to decrypt. That means post-quantum encryption now, even though the tooling is still maturing, even though it is more work.
The identity you generate today should still be defensible in twenty years. That is the bar. Everything in this post is what it takes to clear it with commodity tools, in public, without trusting anyone but yourself.
