· · 14 min read Advanced

Quantum-safe GPG identity with multiple aliases

Your GPG identity is one of the few things AI cannot fake. One offline master, three aliases, post-quantum encryption, layered backups.

A master key in a safe, three keys on her ring, and a static cat creature failing to copy her face

TL;DR One GPG masterkey that only certifies, kept offline. Three aliases bound to it, so anyone can check they’re the same person. Ed25519 subkeys for signing and SSH, an ML-KEM plus X448 subkey for encryption so recorded ciphertext stays closed after a quantum computer shows up, and three backups that each assume the other two are gone. Needs GnuPG 2.5.

A cryptographic signature still means exactly what it says. If the key is yours and the signature verifies, the content came from you. No vendor handed you that identity, no CA can pull it, no platform can suspend it. It exists because you generated the key, and it’s yours for as long as you hold the private half. Most of what gets called “online identity” is borrowed. A handle someone can ban, an email address the domain owner can take back the day they feel like it. A GPG signature sits outside all of that.

For years that was a property crypto nerds cared about and almost nobody else did. That’s changing, and faster than I expected. I used to assume the human behind an email or a commit was a human, and probably the one the headers claimed. I don’t anymore. AI-generated text, voice and video keep getting cheaper, and my inbox already has the results. PR messages that ape a maintainer’s style. A synthetic voice call from a “colleague” who needs a favour. Proving that you wrote the post or shipped the commit isn’t free any more. A signature is the one thing in that pile that can’t be faked without stealing your key first.

There’s 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 record your encrypted traffic today and sit on it, planning to crack it in ten or twenty years when the hardware exists. You can’t un-send the ciphertext you already leaked. You can stop adding to it, and that means post-quantum encryption now, while the tooling is still a bit rough.

So here’s the question I care about. Will the GPG setup you have right now still hold up in a decade? Attribution is a problem today. Long-horizon confidentiality is a problem later. A key you generate today has to survive both.

This post is the setup I landed on after chewing on that. 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? Read GPG explained first. From here on I assume you’ve generated at least one key in your life.

What we’re 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?

ChoiceReason
Masterkey = certify-onlyThe 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 / AuthYou can rotate or revoke each one independently. Laptop stolen? Revoke subkeys, keep identity.
Multiple UIDs on one keyAll three aliases are cryptographically bound to the same master - proof they come from the same person.
ML-KEM (Kyber) for encryptionDefends against harvest-now-decrypt-later. Someone recording your ciphertext today cannot decrypt it in 2046 with a quantum computer.
Ed25519 for signing and authSignatures 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. Dangerous if kapott is supposed to stay pseudonymous, because once that public key hits a keyserver the link is permanent. Keyservers don’t forget, and you can’t ask them to. If you need a real pseudonym, give it its own key (scenario B at the end).

Prerequisites

You need GnuPG 2.5 or later. ML-KEM subkey generation doesn’t 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 paranoid is the right setting here. Nothing your daily laptop might be quietly running 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. It 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 remember

The passphrase is the last thing 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 can’t 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.

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 is 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 and my real mail address). GPG asks for the passphrase twice and you’re done.

Capture the fingerprint right away 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. That signature is the 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 the reason you needed 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’re covered whichever side cracks first, whether classical crypto falls to a quantum computer or somebody finds a hole 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’re still in here, generate a revocation certificate. It’s the kill switch for the day the masterkey is compromised, and you make it now, while the master is still in front of you:

gpg --output "$GNUPGHOME/revocation-$KEYID.asc" --gen-revoke "$KEYID"

Take the master offline

This is the step that makes the rest 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 all of the above was 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 doesn’t knock over the other. The AES-Twofish-Serpent cascade with an Argon2-derived key holds up against a quantum computer too. 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 matter, which is what you want on archival paper. It handles Ed25519 cleanly. It can’t do Kyber yet, since that’s too new and there’s 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 outlasting that is the point. Stick the stack in a fire-resistant box, or split it across two locations if you’re 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 doesn’t 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 it’s a choice worth making on purpose.

Scenario A - add a UID to the existing masterkey. Easy, and it proves cryptographically that 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 don’t, the identities stay cryptographically unrelated. This is my default whenever 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 per identity (dsm-vault.vc, clientX-vault.vc). Same three-layer backup as before, 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 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. The trailing ! 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 push the expiry out again. It’s 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’s the same flow with one extra step. revkey the compromised subkey, export, publish. The master never moved, so your identity is untouched.

The broader principle

None of this is minimal, and I’m 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 all of it, 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. They can revoke it or lose it in an incident. They can be served a subpoena and hand it over. The day they pivot their business model you migrate on their clock instead of yours. I went through this control-versus-convenience question in Sovereign Infrastructure. Owning the root costs me more time than a subscription would. What I get for it is an identity that doesn’t live inside a system I can’t open up and inspect.

Attribution is the same argument, and it’s already live. When text, voice and video can all be faked on hardware anyone can buy, the honest default for any message is “could be anyone.” Signing your commits won’t stop someone churning out garbage in your name. It gives everyone who cares a way to check, and that check holds even when the forgery is otherwise perfect. The check only exists if you were already signing before the forgery turned up, so start now.

Quantum-safety is the same argument stretched across decades. I’m not expecting a quantum computer to read my mail any time soon. I am expecting a patient adversary to record my encrypted traffic today and bet on the hardware showing up later. The only counter is to stop feeding them ciphertext that’ll one day be cheap to crack, which means post-quantum encryption now, rough tooling and all.

My master has been offline since the day I generated it. The subkeys on this laptop expire in two years, and at eighteen months I bring the master back to push that out. The rest is paper in a box and a VeraCrypt file on two clouds. That’s what it took to make a key I expect to still work in 2046.