Put this into practice with the AES-256-GCM Encryptor - encrypt text or files in your browser, with nothing uploaded.
What client-side encryption means
Client-side encryption means the plaintext never leaves the device it was created on. Encryption and decryption happen in the browser, in the mobile app, or on the user's machine - not on a server.
In practice, a web client-side system looks like this: the user enters data, the browser derives or imports an encryption key, encrypts locally with Web Crypto, and only the ciphertext is transmitted, stored, or shared.
The result is a zero-knowledge architecture: the service provider cannot read the data, even if their database is stolen, their logs are subpoenaed, or their infrastructure is compromised.
This is different from TLS. TLS protects data in transit between browser and server, but the server sees the plaintext. Client-side encryption protects data at rest and from the server itself.
The threat model: what it protects, what it doesn't
- Protects: data at rest on a server you do not trust - cloud storage, sync backends, shared documents
- Protects: data in transit that would otherwise be readable by intermediaries with access to the transport
- Protects: database breaches, rogue employees, subpoenas against the provider
- Does NOT protect: a compromised browser, malicious browser extensions, keyloggers, or screen recorders on the user's device
- Does NOT protect: the endpoint itself - if malware runs as the user, it can read data before encryption or after decryption
- Does NOT protect: weak passphrases or badly managed keys - the crypto is only as strong as key hygiene
Warning: Client-side encryption moves the trust boundary from the server to the client environment. If the client is compromised, encryption cannot save you. Document that boundary honestly in your security model.
Web Crypto API: the primitives you need
The Web Crypto API (crypto.subtle) provides standard, audited cryptographic operations in every modern browser. It runs in the same process as the page, but the operations themselves are implemented and reviewed by the browser vendor.
You should use it instead of JavaScript crypto libraries: native implementations are faster and less likely to have subtle bugs, and they support hardware-backed key storage where available.
The primitives that matter for client-side encryption:
- AES-256-GCM - authenticated encryption for payloads, the default choice for text and file encryption
- PBKDF2 - derives a key from a passphrase with configurable cost
- HKDF - derives multiple keys from a single high-entropy input
- RSA-OAEP - asymmetric encryption when you need to encrypt to a public key without a shared secret
- ECDH - key agreement for deriving a shared secret between two parties
- crypto.getRandomValues - cryptographically secure random for salts, IVs, and nonces
How a passphrase-based system works
// Encrypt with Web Crypto (WebCrypto is available in all modern browsers)const enc = new TextEncoder();const salt = crypto.getRandomValues(new Uint8Array(16));const iv = crypto.getRandomValues(new Uint8Array(12));const keyMaterial = await crypto.subtle.importKey( 'raw', enc.encode('passphrase'), 'PBKDF2', false, ['deriveKey']);const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, enc.encode('plaintext'));// Store salt + iv + ciphertext together- Generate a random salt and a random 12-byte IV for each encryption operation.
- Derive a 256-bit key from the passphrase using PBKDF2 with a high iteration count (600,000+ for SHA-256 per OWASP guidance).
- Encrypt the plaintext with AES-256-GCM using the derived key and IV.
- Concatenate and store: salt, IV, and ciphertext (with the GCM authentication tag).
- To decrypt, re-derive the key from the passphrase and the stored salt, then decrypt - the GCM tag fails if the ciphertext or passphrase is wrong.
Key management is the hard part
The algorithm is standardized; key management is where systems fail. A key stored next to the ciphertext, a passphrase written in the source code, or a key reused across documents all defeat the encryption.
Guidelines that apply to any client-side design:
A fresh random IV or nonce for every encryption - never reuse an IV with the same key
A unique random salt per key derivation, so identical passphrases do not produce identical keys
High PBKDF2 iteration counts or a memory-hard KDF such as scrypt or Argon2 for passphrase-derived keys
Never store passphrases or derived keys in source, localStorage, or URL parameters
Provide a recovery story: if the key is lost, the data is unrecoverable - communicate that clearly to users
Support key rotation by design: version the key material and re-encrypt on key change
Note: For shared documents, use hybrid encryption: generate a random content key per document, encrypt the content with AES-GCM, and wrap the content key with each recipient's public key (RSA-OAEP or ECDH). This avoids sharing passphrases and supports per-user revocation.
When client-side encryption is the wrong tool
- Server-side processing is required - search, analytics, server-rendered reports
- You cannot guarantee the client environment (embedded webviews, untrusted devices)
- Data must be recoverable when users lose keys, and you have no acceptable key escrow story
- The threat is endpoint compromise - encryption adds little if malware runs on the same machine
- Compliance requires auditability of plaintext processing by the provider
Production implementation checklist
- Use AES-256-GCM (or ChaCha20-Poly1305) for symmetric payloads - never ECB, never unauthenticated CBC
- Generate IVs/nonces with crypto.getRandomValues, 12 bytes for GCM, never reused
- Derive passphrase keys with PBKDF2 (600,000+ SHA-256 iterations) or Argon2id/scrypt where available
- Authenticate associated data (AAD) that must not be encrypted but must be tamper-proof, such as version numbers
- Store salt, IV, and ciphertext together in a documented, versioned container format
- Fail closed: any integrity failure on decrypt aborts the operation
- Never log plaintext, keys, or passphrases - including in error messages
- Test key rotation, lost-key recovery, and empty-input edge cases
- Set a strict CSP so the page itself cannot be modified by injected scripts
- Audit the code and keep a written threat model alongside it
FAQ
Q.Is client-side encryption safe?
A.Yes, when implemented correctly with standard primitives and sound key management. It is not a silver bullet: the security boundary moves to the client, so endpoint compromise, weak passphrases, and key mismanagement are the real risks.
Q.Is TLS not enough?
A.TLS protects data in transit to your server. The server still sees plaintext, so a breach, a malicious employee, or a legal request can expose it. Client-side encryption protects data from the server itself.
Q.Is Web Crypto trustworthy?
A.Web Crypto uses native, browser-vendor-reviewed implementations of standard algorithms. It is generally safer than bundling JavaScript crypto. The common failure modes are misuse - wrong modes, weak key derivation, IV reuse - not the API itself.
Q.What happens if a user loses their passphrase?
A.The data is permanently unrecoverable, by design. Provide clear warnings, optional key escrow for enterprise use cases, or recovery codes - but never a backdoor, because a backdoor is a vulnerability.
Q.Is browser encryption fast enough for large files?
A.Yes for practical sizes. Web Crypto AES-GCM reaches hundreds of MB/s in modern browsers. For very large files, use streaming (incremental) APIs and Web Workers so the UI stays responsive.
Q.AES-GCM or RSA?
A.Use AES-GCM for the actual data - it is fast and authenticated. Use RSA-OAEP or ECDH to encrypt a random data key to one or more recipients, a pattern called hybrid encryption. Do not encrypt large data directly with RSA.
References
- W3C Web Cryptography API: https://www.w3.org/TR/WebCryptoAPI/
- NIST SP 800-38D – Galois/Counter Mode (GCM): https://csrc.nist.gov/pubs/sp/800/38/d/final
- NIST SP 800-132 – Password-Based Key Derivation (PBKDF2): https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
- RFC 5116 – Authenticated Encryption: https://www.rfc-editor.org/rfc/rfc5116
- OWASP Password Storage Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
Encrypt locally, today
Text or files, AES-256-GCM, entirely in your browser. No upload, no account.
Encryption moves the boundary; key hygiene decides the outcome
Client-side encryption with the Web Crypto API is the right foundation for zero-knowledge products: standard algorithms, clear threat model, and no plaintext on your infrastructure.
Start small: encrypt with AES-256-GCM and a PBKDF2-derived key, ship the container format, and grow into hybrid encryption and key rotation as your product matures.
Try the AES-256-GCM Encryptor to see the pattern in action - your data stays in your browser.