A technical explanation of how SealedKeys protects your secrets. We believe in transparency over security-by-obscurity.
SealedKeys's servers never receive, store, or can compute your plaintext secrets. Every secret is encrypted on your device before being sent. Even if our database were stolen in its entirety, the attacker would have only ciphertext — mathematically useless without your master password.
Your master password is combined with your email address and passed through PBKDF2-SHA256 with 600,000 iterations. This produces your vault key — a 256-bit AES key. This computation happens entirely in your browser using the Web Crypto API.
vaultKey = PBKDF2(masterPassword, email + "sealedkeys_v1", 600000, SHA-256, AES-256-GCM)Each secret's sensitive fields are serialised to JSON and encrypted using AES-256-GCM with a random 12-byte IV (nonce). The output is iv + ciphertext, base64url-encoded.
encryptedData = base64url(iv[12] || AES-GCM(vaultKey, JSON(secretFields)))Only the encrypted blob, the item name (for search), the URL (for favicon matching), and metadata tags. The server can never reconstruct the secret — it lacks the vault key.
Server stores: { name, url, tags, encryptedData } — no plaintext secretsWhen you open your vault, the encrypted blobs are fetched from the server. Your browser derives the vault key again from your master password and decrypts each item locally.
secrets = JSON.parse(AES-GCM-decrypt(vaultKey, base64url-decode(encryptedData)))Your master password is hashed server-side using bcrypt (cost factor 12) solely to verify your identity at login. This hash cannot be reversed to derive your vault key — they are cryptographically independent.
MVP limitation
Currently the master password is transmitted to the server over HTTPS for bcrypt verification. A production implementation should use SRP (Secure Remote Password Protocol) to eliminate any server involvement in the authentication credential entirely. This is on our roadmap.
✓ What we store
✗ What we never store
The encryption implementation lives in lib/crypto.ts and uses only the browser's built-in window.crypto.subtle API — no third-party cryptography dependencies.