Skip to content

Forward Secrecy

Yura Filatov edited this page Jun 27, 2026 · 1 revision

Forward Secrecy

The Problem

Without forward secrecy, every message to a contact is encrypted with the same long-term session key derived from ECDH(ourIdentity, theirIdentity). If either device is compromised, every past and future message between the pair is exposed.


The Solution: Single-Use Prekeys

Occulta uses per-message prekeys to achieve forward secrecy. Each device generates batches of P-256 key pairs in the Secure Enclave, tagged prekey.<contactID>.<uuid>. The public halves are delivered to the contact inside encrypted payloads. When encrypting a message, the sender consumes one of the recipient's prekeys:

1. Pop oldest prekey from contact's stored batch
2. Generate throwaway ephemeral P-256 key pair (in memory, never persisted)
3. Session key = HKDF(ECDH(ephemeralPriv, prekeyPub), ...)
4. AES-GCM.seal(SealedPayload, using: sessionKey, authenticating: AAD)
5. Ephemeral private key discarded immediately

On decryption, the recipient reconstructs the SE tag from the prekey ID in the bundle, retrieves the SE-wrapped private key, derives the same session key, opens the payload, and immediately deletes the prekey from the Secure Enclave. That prekey can never be used again. The session key never existed in persistent storage on either side.


Prekey Delivery

Prekey public keys are never exposed in the clear. They travel exclusively inside AES-GCM-encrypted SealedPayload blobs — the same ciphertext that carries the message content. This is enforced at the type level: WirePrekey only appears inside SealedPayload, never in the unencrypted SecrecyContext (AAD).

New prekey batches are generated reactively: when a device receives a longTermFallback bundle (meaning the sender had no prekeys left to use), it generates a fresh batch and attaches it to the next outbound message. There is no speculative generation.


Quantum Resistance

Prekey public keys are P-256 — classically secure but quantum-vulnerable in isolation. Two layers protect them:

Direct protection: For PQ contacts, every session key (including fallback messages that carry prekey batches) is derived from both ECDH and ML-KEM material. A quantum attacker cannot decrypt the payload containing the prekeys without breaking ML-KEM.

Structural protection: Even if a quantum attacker obtained a prekey public key, they cannot break any message encrypted with that prekey. Every FS session key for PQ contacts includes ML-KEM shared secrets in the HKDF input. Breaking the P-256 ECDH(ephemeral, prekey) is insufficient — the ML-KEM component remains unbroken.

For classical-only contacts (no ML-KEM material), prekeys are protected only by the classical encryption of the payload that delivered them. A future quantum attacker who recorded the exchange could eventually break the P-256 ECDH, recover the prekey public keys, and then break individual messages. Re-exchange in person on iOS 26+ to upgrade to hybrid PQ protection.

Clone this wiki locally