The finding
Post-quantum prekeys were issued one-shot, but the server had no mechanism to retire them after use. The same ML-KEM encapsulation could be replayed, defeating forward secrecy. The fix is the same discipline as classical one-time prekeys: atomically consume on use.
Adding post-quantum protection to a messaging protocol is rarely a single change. Identity keys, signed prekeys, one-time prekeys, the initial root-key derivation, the message ratchet, group key derivation, backups, multi-device sync — each is a place where asymmetric primitives live, and each is its own migration decision.
What we found
In this engagement, the team had correctly added an ML-KEM encapsulation against a post-quantum prekey. But the prekey was treated as reusable: the server returned it, and returned it again, without removing it from the pool when it was consumed.
Why it matters
An attacker with access to the server — or to a single compromised prekey response — could replay the same ML-KEM encapsulation against the same prekey. The shared secret is no longer fresh, and the forward-secrecy property the prekey was supposed to provide is gone.
The fix is not novel. One-time prekeys, classical or post-quantum, must be consumed atomically: the server removes the prekey at the moment it is handed out for a session, and never serves it twice. The post-quantum case is identical — the discipline simply has to be carried across to the new primitive rather than reinvented.
Ignoring the prekey distribution channel
ML-KEM prekeys are ~1184 bytes each. A pool that previously distributed 100 classical prekeys per user now moves over 100 KB per user. Plan the distribution channel — bandwidth, pool sizing, and battery on mobile clients that fetch and verify them.
Have a system that needs this?
Secure my organization