Quick guide for GnuPG 2.2 or newer. Result: a certify-only Ed25519 primary key with separate signing and encryption subkeys. Chapter 4 is optional and only needed if you replace an existing key.
TL;DR: PGP and GPG are not the same. The proprietary PGP product plays no role in FLOSS today, but the OpenPGP standard does.
PGP and GnuPG are compatible because both implement OpenPGP. The precise terms are “OpenPGP key” and “GnuPG”. Many people say “PGP key” casually and mean an OpenPGP key. Others, like us, say “GPG key” and also mean an OpenPGP key. It can be confusing, but we are all talking about the same thing
| Key | Algorithm | Usage | Expiry |
|---|---|---|---|
| Primary | ed25519 | C (certify only) | never |
| Subkey 1 | ed25519 | S (sign) | 2 years |
| Subkey 2 | cv25519 | E (encrypt) | 2 years |
The primary key only certifies. Daily work uses the subkeys. The primary key does not expire, the subkeys expire and are extended later.
gpg --quick-generate-key 'Your Name <you@example.org>' ed25519 cert never
You will be asked for a passphrase.
If the keyring already contains keys with the same address, pick the newest one:
export KEYFP=$(gpg --list-keys --with-colons you@example.org | awk -F: '/^pub/ {c=$6} /^fpr/ && c {print c, $10; c=""}' | sort -n | tail -1 | cut -d' ' -f2)
echo $KEYFP
gpg -K --keyid-format long $KEYFP
Check: ed25519, [C] and today's date. Do not copy fingerprints from web pages or chats, they may contain invisible characters. If in doubt: echo “$KEYFP” | xxd | head -2.
gpg --quick-add-key $KEYFP ed25519 sign 2y gpg --quick-add-key $KEYFP cv25519 encr 2y
Instead of 2y you can give an ISO date (YYYY-MM-DD) or a date with time (YYYYMMDDThhmmss). The time is interpreted as UTC, a trailing Z marks it explicitly. Example for 13:17 local time (CEST, UTC+2) on 2028-10-02:
gpg --quick-add-key $KEYFP ed25519 sign 20281002T111700Z
gpg --edit-key $KEYFP gpg> trust
Choose 5 (ultimate), confirm with y, then quit.
gpg --gen-revoke $KEYFP > revoke-new.asc
Store the revocation certificate offline.
gpg -K --keyid-format long $KEYFP gpg --export $KEYFP | gpg --list-packets | grep -E 'sigclass|key flags'
Expected: sec [C], ssb [S], ssb [E]. Key flags: 01 for the primary key, 02 for the signing subkey, 0C for the encryption subkey.
gpg --armor --export $KEYFP > new-key.asc
Publish new-key.asc (ASCII armor) and show the full fingerprint as text next to the download. Optional link:
<a rel="pgpkey" href="new-key.asc">PGP key</a>
The key must be binary (not armored) and named after the hash of the address:
gpg --with-wkd-hash -k you@example.org gpg --no-armor --export $KEYFP > HASH
Place it at https://example.org/.well-known/openpgpkey/hu/HASH and add an empty file policy one level above. Test:
gpg -v --auto-key-locate clear,wkd,nodefault --locate-key you@example.org
Keys cannot be deleted from keyservers. Upload only when everything is verified.
gpg --keyserver hkps://keys.openpgp.org --send-keys $KEYFP
keys.openpgp.org shows an email address only after verification. For that, upload with:
gpg --export $KEYFP | curl -T - https://keys.openpgp.org
Open the returned link and confirm the mail. keys.openpgp.org does not distribute third-party signatures. For those, also upload to hkps://keyserver.ubuntu.com.
Only needed if the new key replaces an existing one (for example RSA to EC). Order matters: sign, announce, then revoke and publish.
export OLDFP=$(gpg --list-keys --with-colons OLD_KEYID | awk -F: '/^fpr/ {print $10; exit}')
echo $OLDFP
Both $OLDFP and $KEYFP must be full 40-character fingerprints. –quick-sign-key rejects short key IDs (“is not a fingerprint”).
gpg --local-user $OLDFP --quick-sign-key $KEYFP gpg --local-user $KEYFP --quick-sign-key $OLDFP gpg --check-sigs $KEYFP gpg --check-sigs $OLDFP
Each key must show a sig! line from the other key. All UIDs of the old key are signed.
Save as transition-YYYY-MM-DD.txt and insert the full fingerprints (gpg –fingerprint $OLDFP $KEYFP):
KEY TRANSITION STATEMENT - YYYY-MM-DD I am transitioning from my old RSA key to a new key based on elliptic curve cryptography (Ed25519 / Curve25519). OLD KEY (revoked, do not use any more): rsa4096/OLD_KEYID, created YYYY-MM-DD <OLD FINGERPRINT> NEW KEY: ed25519 (certify), created YYYY-MM-DD <NEW FINGERPRINT> Reason: transition to elliptic curve cryptography. The old key has been revoked as superseded. The new key is signed by the old key, and the old key is signed by the new one. This statement is signed with both keys. Please fetch the new key, verify the fingerprint through a second channel if possible, and stop encrypting to the old key. Contact: you@example.org
Sign it with both keys:
gpg --local-user $OLDFP --local-user $KEYFP --clearsign transition-YYYY-MM-DD.txt gpg --verify transition-YYYY-MM-DD.txt.asc
Two “Good signature” lines are expected. The warning “not a detached signature” is harmless when the plain .txt lies next to the .asc. Only the .asc is published.
The statement is optional but useful: a revocation reason is only a short note and does not tell people where to go, while the statement names the successor and is signed by both keys.
gpg --output revoke-old.asc --gen-revoke $OLDFP
Answers: y, reason 2 (Key is superseded), description e.g. Transition to elliptic curve key <NEW FINGERPRINT>, empty line, y, passphrase.
gpg --import revoke-old.asc gpg -k --keyid-format long $OLDFP | head -3
The output must show [revoked: …]. Revocation is final once published. Keep the old key and its revocation, they are needed to verify old signatures and decrypt old data.
gpg --armor --export $OLDFP > old-key-revoked.asc gpg --import-options show-only --import old-key-revoked.asc
The second command must list a rev line for the old key.
old-key-revoked.asc and transition-YYYY-MM-DD.txt.asc next to new-key.asc.gpg –keyserver hkps://keys.openpgp.org –send-keys $OLDFP (and optionally keyserver.ubuntu.com).new-key.asc and transition-YYYY-MM-DD.txt.asc, name both fingerprints in the body.pass, SSH via GPG and similar tools to the new key.gpg –delete-secret-and-public-key FINGERPRINT.shred -u.gpg --quick-set-expire $KEYFP 2y '*'