Encryption Methodology
A complete technical reference for how Loqa protects messages, files, and personally identifiable information โ from device to database and back.
01Encryption at a Glance
Every communication channel in Loqa uses encryption purpose-built for its trust model. The table below summarizes what protects each surface.
| Surface | Protocol | Cipher | Forward Secrecy | Post-Compromise Security |
|---|---|---|---|---|
| 1 : 1 DMs | X3DH + Double Ratchet | AES-256-GCM | โ | โ |
| Group DMs | Per-msg key + Ratchet wrap | AES-256-GCM | โ | โ |
| Server Channels | MLS (RFC 9420) | AES-256-GCM | โ | โ |
| File Attachments | Per-file key wrapping | AES-256-GCM | โ | Via channel protocol |
| PII (Emails) | Server-side envelope | AES-256-GCM + HMAC | N/A | N/A |
| Passwords | Argon2id (PHC winner) | โ | N/A | N/A |
| Stored Files | Server-managed encryption | AES-256-GCM + HKDF | N/A | N/A |
02Direct Message Encryption
1:1 DMs use a Signal-style protocol with two phases: X3DH for initial key agreement and the Double Ratchet for ongoing message encryption.
X3DH Key Agreement
When Alice first messages Bob, the Extended Triple Diffie-Hellman (X3DH) protocol establishes a shared secret without requiring Bob to be online.
Curve
X25519 (Curve25519) with automatic P-256 ECDH fallback for browsers that don't yet support X25519 in WebCrypto.
KDF
HKDF-SHA256 with the info label loqa-x3dh. The raw DH outputs are concatenated and passed through HKDF to derive the initial root key.
Signed PreKey
Bob's signed prekey is authenticated by signing the raw public key bytes with his identity key, preventing key substitution attacks.
One-Time PreKeys
Uploaded in batches of 100, replenished when fewer than 30 remain. Each is consumed once to provide an additional layer of key separation.
Double Ratchet Protocol
After X3DH establishes the shared secret, every subsequent message uses the Double Ratchet โ a combination of a symmetric chain ratchet and a DH ratchet that provides forward secrecy and post-compromise security.
Chain KDF
Each chain key is advanced using HMAC-SHA256 with two constant inputs: 0x01 produces the message key, 0x02 produces the next chain key. Message keys are used once and discarded.
DH Ratchet
When a reply is sent, a new DH key pair is generated. The new DH output is combined with the root key via HKDF to derive a fresh root key and chain key โ providing post-compromise security.
Out-of-Order Messages
Up to 256 skipped message keys are cached per session, enabling decryption of out-of-order messages without breaking the ratchet state.
Multi-Device
Each device maintains its own ratchet session. Messages are encrypted for all of the recipient's devices, with per-device ciphertext carried in the message header.
03Group DM Encryption
Group DMs use a per-message random key architecture. Each message gets a fresh AES-256-GCM key, which is then wrapped (encrypted) individually for each participant using their ratchet session.
via Ratchet session
via Ratchet session
via Ratchet session
Ratchet-Wrapped Keys
Each recipient's copy of the message key is encrypted using their active Double Ratchet session โ inheriting the same forward secrecy and post-compromise security as 1:1 DMs. The wrapped key format is a JSON object containing the ratchet header, ciphertext, and nonce.
Legacy Fallback
If no ratchet session can be established for a recipient (e.g., they haven't uploaded prekeys), the key is wrapped using static ECDH with ciphertext:nonce format. This path will be phased out as all clients upgrade.
04Server Channel Encryption (MLS)
For server channels that opt into E2EE, Loqa implements Messaging Layer Security (MLS) per RFC 9420 via an OpenMLS WASM module running entirely in the browser.
Server Role
The Loqa server acts as a "dumb pipe" Delivery Service โ it stores opaque encrypted bytes and fans them out to group members. It never sees plaintext or group secrets.
KeyPackages
Each client pre-uploads KeyPackages (minimum 10, replenished in batches of 20) so other members can add them to groups asynchronously.
Epochs
Every membership change (join, leave, remove) advances the group epoch. All keys from previous epochs are discarded, ensuring forward secrecy for the group.
Commits
Membership changes are applied via MLS Commit messages, which atomically update the ratchet tree and derive new application secrets.
05File Encryption
Every file attachment โ in DMs, Group DMs, or E2EE server channels โ is encrypted client-side before upload. The storage provider never sees plaintext.
AES-256-GCM key
with per-file key
to storage
pairwise shared secret
via Ratchet sessions
as a group message
Key Wrapping (DM)
The per-file key is exported to raw bytes, then encrypted with the pairwise AES-256-GCM shared secret using a fresh 12-byte nonce. The wrapped key and wrap nonce are sent as metadata alongside the message.
Server-Managed File Encryption
For non-E2EE server channels, the backend encrypts files at rest using AES-256-GCM with per-file keys derived from the server master key via HMAC-SHA256 (loqa-file:{fileId}). Files are stored as nonce (12 bytes) โ ciphertext.
06PII & At-Rest Protection
Loqa never stores PII in plaintext. A master encryption key protects all server-side sensitive data, with domain-separated sub-keys ensuring cryptographic isolation between systems.
HMAC-SHA256("loqa-email-enc" โ 0x01)HMAC-SHA256("loqa-email-hmac" โ 0x01)HMAC-SHA256("loqa-file:{id}")Email Encryption
Email addresses are encrypted with AES-256-GCM using a derived sub-key and a random 12-byte nonce. The ciphertext and nonce are stored base64-encoded. We can authenticate you without ever storing your email in plaintext.
Blind Index Lookup
Emails are normalized (lowercase + trim), then hashed with HMAC-SHA256 using an isolated sub-key. This deterministic blind index enables login and deduplication queries without revealing the actual address.
Password Hashing
Argon2id โ the Password Hashing Competition winner โ protects all user passwords and OAuth2 client secrets. Each hash uses a unique random salt via OsRng. API tokens and webhook secrets use SHA-256 with constant-time verification.
Key Isolation
The master key is never used directly. All operations use domain-separated sub-keys derived via HMAC-SHA256 (equivalent to HKDF-Expand). Compromising one key domain does not affect the others.
07Key Storage & Lifecycle
Private keys are generated, stored, and used entirely on-device via the Web Crypto API and IndexedDB. They never leave the device and are never transmitted to Loqa's servers.
Identity Key Pair
Non-extractable X25519 key pair stored in IndexedDB (loqa_e2ee/keypairs). Generated once on first login; public key uploaded to the server.
Ratchet Sessions
Per-(peer, device) session state stored in loqa_e2ee/ratchet_sessions. Contains root key, chain keys, ratchet key JWK, and up to 256 skipped message keys.
Signed PreKeys
Stored in loqa_e2ee/signed_prekeys. Public key is signed by the identity key and uploaded to the server. Private JWK stays local for X3DH responder operations.
One-Time PreKeys
Batched in loqa_e2ee/otp_prekeys (100 at a time, replenished at 30). Each public key is uploaded; private JWK stays local and is consumed on first use.
MLS Group State
Persisted in a separate loqa-mls IndexedDB. Tracks group epoch and identity initialization state. WASM manages in-memory tree state.
Device Identity
Each device generates a unique ID stored in loqa_e2ee/device_info. This allows per-device ratchet sessions and multi-device message delivery.
08Reproducible Audit Scope
Every cryptographic claim in this whitepaper can be independently verified by reading 8 self-contained source files. These files have zero business logic dependencies โ they rely only on the Web Crypto API (browser) and standard Rust crates (server).
| File | Language | Purpose | Lines |
|---|---|---|---|
crypto.ts | TypeScript | X25519/P-256 ECDH key exchange + AES-256-GCM encrypt/decrypt primitives | 138 |
doubleRatchet.ts | TypeScript | Full X3DH key agreement + Double Ratchet protocol implementation | 615 |
e2eeManager.ts | TypeScript | Orchestration โ DM, Group DM, and MLS file encryption flows | 927 |
fileCrypto.ts | TypeScript | Per-file AES-256-GCM encryption with key wrapping | 145 |
keystore.ts | TypeScript | IndexedDB private key storage (proves keys never leave device) | 289 |
mlsManager.ts | TypeScript | OpenMLS WASM wrapper โ group management, encrypt, decrypt | 333 |
mlsStorage.ts | TypeScript | IndexedDB persistence for MLS group & identity state | 114 |
crypto.rs | Rust | Server-side PII encryption, blind indexes, Argon2id, file encryption | 208 |
| Total auditable surface | 2,762 | ||
For security researchers: These 8 files contain 100% of the cryptographic logic. No encryption decisions are made outside this boundary. To validate any claim in this whitepaper, search for the relevant function in these files.
๐ View source on GitHub โ loqachat/loqa-encryption-audit
Questions About Our Encryption?
We welcome review from security researchers, cryptographers, and enterprise security teams. We're happy to provide additional technical detail or discuss integration requirements.