Account credentials, passwords, and sensitive data — encrypted with ECDH P-256 and AES-GCM-256 entirely in your browser. The server only ever stores ciphertext.
Every design decision prioritises your privacy. Your keys never leave your browser.
Data is encrypted with AES-GCM-256 before any network request. Each field gets a unique random IV — replay attacks are impossible.
AES-GCM-256 · Unique IV per fieldThe server stores only ciphertext. No encryption keys, no plaintexts, no passphrases ever touch the backend — not even during setup.
Server sees: ciphertext onlyYour wrapping key is derived from your passphrase using PBKDF2 with 200,000 iterations and a user-specific salt, making brute-force infeasible.
PBKDF2 · SHA-256 · 200k iterationsOrganisation keys are wrapped per-user via ephemeral ECDH P-256 key exchange. Adding a new team member re-wraps the org key — it's never transmitted in the clear.
ECDH P-256 · Ephemeral key exchangeLose your session? Your passphrase re-derives exactly the same wrapping key using PBKDF2. No key escrow, no recovery codes — by design.
Deterministic · No key escrowFrom passphrase to encrypted field — four deterministic steps.
Your passphrase is used locally — it is never sent to the server. PBKDF2 (SHA-256, 200,000 iterations) derives a deterministic AES-GCM-256 wrapping key from your passphrase and a salt generated from your user ID.
PBKDF2(passphrase, SHA-256(userId), 200_000, 32, SHA-256)Your ECDH P-256 private key is stored encrypted (AES-GCM) in the database. The wrapping key decrypts it client-side. A failed AES-GCM tag means an incorrect passphrase — no roundtrip needed.
privateKey = AES-GCM.decrypt(wrappingKey, encryptedPrivKey)The org key (32 random bytes) was wrapped for you via ephemeral ECDH. Your private key performs the ECDH agreement, recovering the shared secret, which decrypts the org key — all in the browser.
orgKey = AES-GCM.decrypt(ECDH(privateKey, ephPub), wrappedOrgKey)Every sensitive field (passwords, email credentials, account data) is encrypted with AES-GCM-256 using the org key. Each field has a unique 12-byte random IV prepended to the ciphertext.
plaintext = AES-GCM.decrypt(orgKey, IV || ciphertext)Invite-only access. Every account is end-to-end encrypted from day one.