For an applied cryptography project at NYU (CS6903), I built an end-to-end encrypted channel between two endpoints — the classic Alice ↔ Bob setup — with one twist: I implemented it twice, with two completely different families of post-quantum primitives, and then attacked and benchmarked both under the same test harness.
The premise sounds academic until you remember what post-quantum migration actually looks like in the real world. NIST has standardized ML-KEM (Kyber) and ML-DSA (Dilithium), and almost everyone is deploying exactly those. Which means almost everyone now shares a single point of cryptanalytic failure: structured lattices. If lattices fall, they all fall together. The interesting engineering question isn't "can I integrate Kyber?" — it's "how expensive is it to not bet everything on one mathematical assumption?"
Both channels target NIST post-quantum security Category 3 and share the same symmetric layer. Only the asymmetric primitives differ:
Everything hides behind one pq_backend_t interface in C, selected at runtime with --backend A or --backend B. That interface is the actual deliverable, in my opinion. Getting it right forced every protocol decision — transcript hashing, key schedules, message framing — to be primitive-agnostic. That's what crypto-agility means in practice: not "we can swap libraries," but "nothing above the KEM knows or cares which KEM it is."
Running both backends through the same Docker harness makes the trade-offs concrete rather than theoretical:
The takeaway isn't "Scheme B is bad." It's that the diversified channel costs you a constant factor on the handshake and almost nothing after it, because the symmetric layer dominates steady-state traffic. For a long-lived session, mathematical diversity is close to free. For handshake-heavy workloads, it's a real bill.
The project required attack demos, and this is where most of my actual learning happened. Three classes of attacks, run as automated tests (make test fails the build if any attack succeeds):
The primitives were never the weak point. Every bug I found lived in the protocol glue — ordering, framing, negotiation — the parts no NIST competition evaluates.
The full implementation, attack suite, and benchmark harness are on GitHub — it runs anywhere Docker does: lutherleo/Quantum-safe-Kyber-Crystals.