-
Notifications
You must be signed in to change notification settings - Fork 0
Post Quantum Protection
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.
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.
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.
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.
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.
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.