Skip to content

Post Quantum Protection

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

Post-Quantum Protection

The Threat

Occulta's classical key agreement uses ECDH P-256. Shor's algorithm on a sufficiently powerful quantum computer can derive a P-256 private key from its public key in polynomial time. An adversary who records public keys exchanged today could decrypt all associated messages years from now when quantum computers become capable. This is the "harvest now, decrypt later" attack.


The Defense: Hybrid ECDH + ML-KEM-1024

On devices running iOS 26 or later, Occulta performs a hybrid key agreement during the in-person exchange that combines classical ECDH P-256 with ML-KEM-1024 (NIST FIPS 203, Security Level 5). ML-KEM is a lattice-based Key Encapsulation Mechanism with no known quantum speedup.

The ML-KEM-1024 private key is generated inside the Secure Enclave via SecureEnclave.MLKEM1024.PrivateKey. All decapsulation operations are performed by the SE chip internally — the private key never exists in app memory. After decapsulation completes, the reference is released and the key becomes inaccessible.


Mutual Encapsulation (Option A)

Both devices generate an ephemeral ML-KEM-1024 key pair during the exchange. Both sides encapsulate against the other's public key, producing two independent shared secrets.

Alice → Bob:  ML-KEM public key (1,568 bytes)
Bob → Alice:  ML-KEM public key (1,568 bytes)
Alice → Bob:  ML-KEM ciphertext (1,568 bytes)  — Bob decapsulates → secret_AB
Bob → Alice:  ML-KEM ciphertext (1,568 bytes)  — Alice decapsulates → secret_BA

Both sides now hold secret_AB and secret_BA. These are sorted lexicographically and concatenated with the ECDH shared secret before a single HKDF pass. Neither side has a privileged role — the protocol is fully symmetric.


Storage

All ML-KEM artifacts from the exchange — both shared secrets and both ciphertexts — are grouped in a single QuantumKeyMaterial struct, encrypted as one AES-GCM blob, and stored on the contact record in SwiftData. The encryption key is the hybrid local DB key (SE + random component). The ML-KEM shared secrets are application-layer encrypted like all other contact data.


Quantum Protection of Forward-Secret Messages

For PQ contacts, every forward-secret message is directly quantum-resistant. The session key for each forward-secret message is derived from both the classical ECDH (ephemeral × prekey) and the ML-KEM shared secrets from the original exchange:

IKM  = ECDH(ephemeralPriv, prekeyPub) || sorted(ML-KEM_secret_1, ML-KEM_secret_2)
Salt = XOR(prekeyPub, ephemeralPub)
Info = "Occulta-v2-hybrid-pq-fs-transport-2026"

A quantum attacker who can break the P-256 ECDH between the ephemeral key and the prekey still cannot derive the session key without also recovering the ML-KEM shared secrets. Those secrets are stored encrypted in SwiftData and are never transmitted on the wire after the initial exchange.

For the long-term fallback path (when prekeys are exhausted), the same direct hybrid construction applies — ML-KEM secrets are mixed into the fallback session key via kHybridTransportKeyInfo. Every message to a PQ contact, whether forward-secret or fallback, is independently quantum-resistant.


Backward Compatibility

PQ capability is negotiated implicitly via optional fields in the exchange message — not via a version bump. A v1 peer's JSON decoder silently ignores the encapsulationKey, nonce, and ciphertext fields it doesn't recognize. The exchange completes as classical, and messages are encrypted with the classical derivation path.

On iOS < 26, the PQ provider is nil. The device sends its identity without an ML-KEM public key and falls back to classical on receive. No crash, no error, no degraded UX — just classical security.

Contacts exchanged before the PQ upgrade retain their classical-only key material. They can re-exchange in person to establish hybrid PQ protection.

Clone this wiki locally