Post-Quantum Encryption for Production Systems
A Practical Playbook
By Byiringiro Thierry · 2026-04
Post-Quantum Encryption for Production Systems
A Practical Playbook Byiringiro Thierry · April 2026
1. Abstract
NIST finalized ML-KEM (formerly Kyber) and ML-DSA (formerly Dilithium) in August 2024, with SLH-DSA finalized in parallel. Two years on, the production ecosystem has barely moved. The default TLS handshake in 2026 is still X25519 with Ed25519 certificates; the default JWT signature is still RS256 or ES256; the dominant cloud KMS APIs still expose RSA and ECDSA as their first-class primitives.
This is a problem. Not because cryptographically-relevant quantum computers exist yet — they do not — but because the harvest-now-decrypt-later threat is rational today for any adversary willing to store traffic with a multi-decade horizon. Long-lived ciphertexts (medical records, government archives, intelligence, identity systems) recorded in 2026 are at-risk by 2035-2040 at the latest, and most analysts now place that window at sub-decade scale for nation-state adversaries.
I argue that the right migration path is hybrid envelope encryption — classical primitives composed with post-quantum primitives, such that an adversary must break both to win. This is what @tigthor/kokocrypt ships today, and what I expect the industry default to be by 2028. The remainder of this paper lays out the construction, the engineering trade-offs, and the migration timeline.
2. The threat model, calibrated
We do not need to assume the existence of a fault-tolerant quantum computer with 4,000+ logical qubits to motivate migration. We need only assume:
- An adversary with the operational patience to record traffic for 10+ years.
- Continued non-monotonic but real progress in quantum hardware (IBM 1,121 qubits 2023, Atom Computing 1,180 qubits 2024, Quantinuum H2 production-grade error-correction 2025).
- The continued steady improvement of Shor's algorithm and variant attacks against lattice-light schemes (which has held for the last decade).
Under these assumptions, ECDH and RSA key exchanges performed in 2026 are not safe for any data the user expects to remain confidential through 2040. That bound is conservative — it does not require nation-state-grade compute. It only requires one large quantum computer, operated once, against a stored trace.
This is the threat model @tigthor/kokocrypt is built for. Not "the quantum apocalypse arrives in 2027," but "treat data you encrypt today as if a future adversary can replay your handshake on a quantum machine in 2035."
3. The hybrid construction
The deceptively simple insight: if you encrypt the same plaintext to both a classical KEM (X25519) and a post-quantum KEM (ML-KEM-768) and combine the resulting shared secrets via a KDF, an attacker must break both to recover the message. Concrete instantiation:
// kokocrypt/envelope.ts (simplified)
import { x25519 } from "@noble/curves/ed25519";
import { mlkem768 } from "@noble/post-quantum";
import { hkdf } from "@noble/hashes/hkdf";
import { sha256 } from "@noble/hashes/sha256";
import { gcm } from "@noble/ciphers/aes";
export function sealHybrid(
pkClassical: Uint8Array, // X25519 recipient pk
pkPQ: Uint8Array, // ML-KEM-768 recipient pk
plaintext: Uint8Array,
): HybridCiphertext {
// ephemeral classical
const eskClassical = x25519.utils.randomPrivateKey();
const epkClassical = x25519.getPublicKey(eskClassical);
const ssClassical = x25519.getSharedSecret(eskClassical, pkClassical);
// post-quantum encapsulation
const { cipherText: pqCt, sharedSecret: ssPQ } = mlkem768.encapsulate(pkPQ);
// combine secrets with HKDF — DOMAIN-SEPARATED
const combinedSeed = new Uint8Array(ssClassical.length + ssPQ.length);
combinedSeed.set(ssClassical, 0);
combinedSeed.set(ssPQ, ssClassical.length);
const dek = hkdf(sha256, combinedSeed, /* salt */ undefined, "kokocrypt/v1/dek", 32);
// authenticated symmetric encryption
const nonce = crypto.getRandomValues(new Uint8Array(12));
const ct = gcm(dek, nonce).encrypt(plaintext);
return { epkClassical, pqCt, nonce, ct };
}
Three subtleties matter in production:
3.1. Domain-separated KDF input. Concatenating ssClassical || ssPQ and feeding it to HKDF with a versioned info parameter is non-negotiable. Earlier hybrid drafts (TLS-Hybrid in 2018-2020) used XOR or simple concatenation without domain separation; both have variants that allow attacker-controlled corruption. The HKDF approach is what RFC 9590 (TLS 1.3 Hybrid Key Exchange, Sept 2024) ultimately standardized.
3.2. ML-KEM-768, not ML-KEM-512 or ML-KEM-1024. 768 is the NIST Category 3 parameter set — equivalent to AES-192 security. 512 is too aggressive for long-term archives; 1024 doubles ciphertext size with marginal security gain. 768 is the right default for almost all production systems and is what TLS 1.3 hybrid suites have converged on.
3.3. Stateless KEMs only. Avoid Kyber-style schemes that require persistent state on the encryptor side. The IND-CCA2 security proof depends on the encapsulator being stateless. kokocrypt enforces this at the type level by accepting only Uint8Array recipient public keys; no session handles.
4. Performance: what to budget
Production engineers ask the same first question: what does this cost me? Here are the measured numbers from kokocrypt v1.0.1 on a 2024 Apple M3 Max, Node.js 22, single-threaded:
| Operation | Classical (X25519 + AES-256-GCM) | Hybrid (X25519 + ML-KEM-768 + AES-256-GCM) | Overhead |
|---|---|---|---|
| Encrypt 1KB | 38 µs | 81 µs | 2.1× |
| Encrypt 1MB | 1.3 ms | 1.4 ms | 1.08× |
| Decrypt 1KB | 36 µs | 89 µs | 2.5× |
| Ciphertext size (1KB plaintext) | 1,064 B | 2,232 B | +1,168 B |
| Public-key size | 32 B | 1,216 B | +1,184 B |
Read these the right way. The bulk-encryption path is dominated by AES-GCM — adding post-quantum KEM costs ~50 µs per envelope. For a request-per-second-bound service (an API gateway), this is irrelevant: the per-handshake cost is one-shot, amortized over a session's worth of AES-GCM frames. For a hash-per-second-bound service (a Merkle log, a content-addressed store), this is similarly irrelevant — you encrypt at object boundaries, not block boundaries.
Public-key sizes are where it bites. A 1.2 KB recipient public key in a database or a JWT is a 38× increase over the X25519 baseline. Storage budgets, certificate pinning sets, and DNS TXT records all need to be re-budgeted. For TLS 1.3 hybrid handshakes, the round-trip overhead is real: a 2,400-byte ClientHello pushes past one MTU and incurs a fragmentation/reassembly cost on slow links.
5. Signatures: the parallel migration
KEMs handle confidentiality. Signatures handle authenticity. The post-quantum migration story for signatures is messier:
- ML-DSA (Dilithium-3) is the NIST-finalized lattice signature. Signature size: 3,309 bytes. Public key: 1,952 bytes. Signing speed comparable to Ed25519. Verification ~3× slower.
- SLH-DSA (SPHINCS+-128s) is the hash-based fallback. Signature size: 7,856 bytes. Slow signing (~10× Ed25519). Conservative — built on Merkle trees and one-time signatures, with security relying only on SHA-2.
- FALCON (FN-DSA in NIST nomenclature) is more compact (~666 bytes) but has implementation pitfalls — its sampler is notoriously sensitive to side-channel leaks.
For most systems, start with ML-DSA. Use SLH-DSA only for root-of-trust signatures (CA roots, software supply chains) where signature size is irrelevant and the conservative security argument matters.
The painful bit is legacy verifier deployment. If you sign with ML-DSA today, every client that needs to verify must understand ML-DSA. The standard hybrid pattern for signatures is: produce both an Ed25519 and an ML-DSA signature, ship them as a tuple, and have verifiers check whichever they support. This is what Web PKI is converging on and what kokocrypt's signHybrid() API ships today.
6. Operational concerns: keys, KMS, rotation
The interesting engineering problem in PQ migration is not the math — it's the key-management plumbing. Four concrete gotchas from shipping kokocrypt:
6.1. KMS APIs are not ready. AWS KMS, Google Cloud KMS, and Azure Key Vault have all announced post-quantum roadmaps but as of April 2026, none expose ML-KEM or ML-DSA as first-class operations. The pragmatic workaround is to use a hardware-backed classical key inside the KMS as wrapping material and store the actual PQ key material in the KMS's generic secret-storage API. The trust boundary remains the KMS; the operations remain in user code.
6.2. Key rotation is harder when keys are 1.2 KB. Many systems assume key blobs fit in a single Postgres row, a single Vault secret, or a single ENV variable. ML-KEM-768 public keys exceed common limits in surprising places: ENV-var maximum sizes on some Linux distros (4096 chars after base64), JWT header maximums in some load balancers (8 KB), and DNS TXT record limits (255 bytes per string, 65,535 total per record). Audit your key paths before you migrate.
6.3. Algorithm-agility is now mandatory. The lesson of TLS 1.0/1.1 deprecation is that hard-coded primitive choices cost years to undo. PQ migration is the right time to enforce algorithm tags on every signed/encrypted object. kokocrypt writes a 4-byte algorithm marker into the first bytes of every ciphertext; the decryption path dispatches on it. Without this, your 2026 ciphertexts will be technical debt in 2032 when ML-KEM-1024 or post-Kyber primitives arrive.
6.4. JWTs need a profile, not a flag. Adding "alg": "ML-DSA-65" to JWT headers works in theory and breaks half of all parsers in practice. Most production deployments wrap PQ-signed payloads inside a JWS envelope — a classical-signed outer JWT whose payload is a PQ-signed inner claim. Backwards-compatible, debuggable, and migration-friendly.
7. Migration timeline
A realistic five-stage plan for a mid-size production system:
| Stage | Window | Action | KPI |
|---|---|---|---|
| 1 | 2026 Q3 | Inventory: every place ciphertext or signature is produced + stored | Asset map complete |
| 2 | 2026 Q4 | Hybrid encryption at the storage layer for new writes; old reads continue classical | % of new objects hybrid > 95% |
| 3 | 2027 Q1–Q2 | Hybrid signatures on all new JWT / API-key issuance; verifiers accept both | % of issued tokens hybrid > 95% |
| 4 | 2027 Q3 | TLS-Hybrid (X25519 + ML-KEM-768) at all internet-facing edges | % of TLS handshakes hybrid > 90% |
| 5 | 2028 Q1–Q2 | Rotate-encryption sweep on long-lived stored data (PII, archives, key material) | < 1% legacy ciphertexts remaining |
Plan stage 2 first. It is the highest-leverage, lowest-risk step: new writes are hybrid-encrypted, old reads remain decryptable, and there is no client-facing latency change.
8. Conclusion
Post-quantum cryptography is no longer a research problem. The primitives are standardized, the reference implementations are production-quality, and the threat model — harvest-now-decrypt-later — is operational today. The only remaining barrier is engineering plumbing: KMS support, key-size budgets, algorithm-agility frameworks, and the discipline to do the migration in five stages instead of one panicked sprint.
@tigthor/kokocrypt is one possible expression of this playbook. The thesis it embodies is broader than any one library: encrypt and sign for 2035, not 2026. That is the right horizon for any data you care about.
References
- NIST FIPS 203 — Module-Lattice-Based Key-Encapsulation Mechanism Standard (August 2024)
- NIST FIPS 204 — Module-Lattice-Based Digital Signature Standard (August 2024)
- NIST FIPS 205 — Stateless Hash-Based Digital Signature Standard (August 2024)
- RFC 9590 — Hybrid Key Exchange in TLS 1.3 (September 2024)
- Bernstein, Lange — Post-Quantum Cryptography: A Practical Survey (2024)
- Stebila & Mosca — Post-Quantum Key Exchange for the Internet and the Open Quantum Safe Project (updated 2024)
- Sikeridis et al. — Post-Quantum Authentication in TLS 1.3: A Performance Study (2024)