A layered implementation of encrypted messaging from PGP's 1991 origins to post-quantum hybrid encryption. Each layer is a working Python implementation that demonstrates what changed, why, and what the tradeoffs are.
This is not a wrapper around GPG. The packet formats, handshakes, and key
derivation are written from scratch. The cryptography library is used only
for primitives (AES, ChaCha20, X25519, Ed25519, HKDF, etc.).
Read HISTORY.md first. It tells the story behind each layer.
| Directory | Standard | Key Exchange | Symmetric | Integrity |
|---|---|---|---|---|
classic/ |
PGP 1991-era | RSA-2048-OAEP | AES-128-CFB | None (demonstrates the weakness) |
openpgp_v4/ |
RFC 4880 | X25519 ECDH | AES-256-GCM | SEIPD v1 + MDC |
openpgp_v6/ |
RFC 9580 (2024) | X25519 ECDH | AES-256-GCM | SEIPD v2 mandatory AEAD |
age_like/ |
age v1 | X25519 | ChaCha20-Poly1305 | AEAD + header MAC |
pq_hybrid/ |
age v1 extended | X25519 + ML-KEM-768 | ChaCha20-Poly1305 | AEAD + header MAC |
pip install -e ".[dev]"
The post-quantum layer additionally requires liboqs and pyoqs:
pip install pyoqs
liboqs must be installed on your system. See the Open Quantum Safe project at https://openquantumsafe.org for installation instructions.
pytest
The pq_hybrid tests are skipped automatically if pyoqs is not installed.
Each layer has a CLI entry point installed by pip install -e ..
pgp-classic keygen --out mykey
pgp-classic encrypt --pub mykey.pub.pem --in message.txt --out message.asc
pgp-classic decrypt --priv mykey.priv.pem --in message.asc
pgp-v4 keygen > mykey.json
pgp-v4 encrypt --key-json mykey.json --in message.txt --out message.asc
pgp-v4 decrypt --key-json mykey.json --in message.asc
pgp-v6 keygen > mykey.json
pgp-v6 encrypt --key-json mykey.json --in message.txt --out message.asc
pgp-v6 decrypt --key-json mykey.json --in message.asc
age-enc keygen > identity.txt
age-enc encrypt -r age1... --in message.txt --out message.age
age-enc decrypt -i identity.txt --in message.age
pq-enc keygen > keys.json
pq-enc encrypt --key-json pub.json --in message.txt --out message.pqage
pq-enc decrypt --key-json priv.json --in message.pqage
This is not a drop-in GPG replacement. It is a study in protocol design. Several deliberate simplifications are made:
- Key management (keyservers, key signing, revocation) is not implemented. Those systems are interesting failures worth studying separately.
- The classic layer's lack of integrity protection is intentional. It is there to demonstrate the attack surface, not to be used.
- AES-256-OCB is approximated with AES-256-GCM in the v6 layer because Python's cryptography library does not expose OCB directly. A production implementation would use cffi bindings to OpenSSL's EVP_OCB.
The progression from classic to pq_hybrid traces the actual history of the field:
- RSA + no integrity = the original problem
- ECDH + MDC = modern GPG, mostly correct
- Mandatory AEAD = what the standard should have required from the start
- Throw out the complexity = age's answer to the complexity argument
- Hybrid PQ KEM = the answer to the quantum threat, deployed today by Cloudflare
Each layer makes one or two key improvements over the previous one. Reading them in order is more instructive than reading a single correct implementation.
- RFC 4880 -- OpenPGP Message Format
- RFC 9580 -- OpenPGP (2024)
- FIPS 203 -- ML-KEM (Module-Lattice Key Encapsulation Mechanism)
- age file format v1: https://age-encryption.org/v1
- Open Quantum Safe project: https://openquantumsafe.org
- "The PGP Problem" -- Latacora (2019)