The WebCrypto API gives you fast, native cryptography right in the browser. It’s powerful, secure, and way better than rolling your own crypto in JavaScript — but only if you use it correctly. A small mistake can silently weaken your app.
Here’s a practical guide to using WebCrypto the right way.
WebCrypto Only Works on HTTPS (Seriously)
Let’s get the basics out of the way: WebCrypto runs only in secure contexts — HTTPS or localhost.
If your site is served over plain HTTP, the API is blocked. Browsers don’t want to expose crypto features in an environment where the page could be modified in transit.
So if someone tries to test WebCrypto on a raw http:// site and everything returns errors… yeah, that’s expected.
What WebCrypto Provides
You get access to native implementations of:
-
Secure random numbers (
crypto.getRandomValues) - Symmetric encryption (AES-GCM)
- Asymmetric keys (RSA, ECDSA)
- Key derivation (PBKDF2, HKDF)
- Hashing (SHA-256/384/512)
- Signing and verification
Everything runs in the browser’s crypto subsystem, making it faster and safer than typical JS libraries.
Common Pitfalls (and How to Avoid Them)
1. Use AES-GCM, not AES-CBC
CBC mode is fragile and easy to misuse. GCM gives you authenticated encryption, protecting both confidentiality and integrity.
2. Never reuse IVs in AES-GCM
GCM breaks if you reuse an IV with the same key. Generate a fresh 12-byte IV every time:
jsconst iv = crypto.getRandomValues(new Uint8Array(12));
Store it alongside the ciphertext.
3. Generate non-extractable keys
If a key doesn’t need to leave the browser, generate it as non-extractable:
jscrypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
"encrypt",
"decrypt",
]);
This protects against accidental exposures through logs or XSS.
4. Don’t hash passwords directly
Never turn a password into a key using SHA-256. It’s weak and brute-forceable.
Use PBKDF2 instead:
jsconst key = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 200_000,
hash: "SHA-256",
},
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
Practical Usage Patterns
Secure token generation
jsfunction generateToken(size = 32) {
const buf = new Uint8Array(size);
crypto.getRandomValues(buf);
return btoa(String.fromCharCode(...buf));
}
Encrypting JSON properly
jsasync function encryptJSON(key, obj) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const data = new TextEncoder().encode(JSON.stringify(obj));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
data
);
return { iv, ciphertext };
}
Signing data with HMAC
jsconst key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
Use HMAC for tamper detection when you don’t need encryption.
Safe Storage Tips
- Don’t store secrets in LocalStorage. Use non-extractable keys in IndexedDB.
- Don’t store plaintext passwords. Ideally, hash them server-side.
- WebCrypto helps with local encryption, but remember: client-side crypto does not protect against XSS. If an attacker runs JS on your page, they can use your keys.
When WebCrypto Isn’t the Right Tool
Avoid using it when:
- You want to hide secrets from JS itself (impossible — XSS breaks everything).
- You need deterministic encryption.
- You want easy interoperability with another runtime without extra encoding/format work.
Use WebCrypto when you want:
- Secure local data storage
- End-to-end encryption for synced data
- Token generation
- Signatures and verification
Final Thoughts
WebCrypto is the safest way to do cryptography in the browser. Stick to authenticated encryption, generate IVs properly, use key derivation instead of plain hashes, and always run under HTTPS.
Album of the blog:




