What Web Crypto is, in plain words
Modern browsers come with a built in cryptography toolkit. You did not install it, it is already there. It can hash data, encrypt and decrypt, sign and verify signatures, and generate cryptographically random numbers.
This matters because before browsers shipped this, anyone who needed to do crypto in a web page had to load a JavaScript library. Those libraries were big, slow, and frequently bugged in subtle ways. Now you have a built in version that is faster, audited, and shared across every site.
The toolkit is called the Web Crypto API. The interesting part of it lives at window.crypto, and the heavyweight operations live at window.crypto.subtle.
What it actually gives you
Three useful things, in order of how often you will reach for them.
Random numbers. crypto.getRandomValues() fills an array with cryptographically secure random bytes. This is what you use to generate session ids, tokens, password salts, anything where the predictability of plain Math.random() would be a security problem.
Hashing. crypto.subtle.digest() computes SHA-256, SHA-384 and SHA-512. You give it bytes, it gives you a hash. Useful for content fingerprints, integrity checks, or quick equality of two big chunks of data without comparing them byte by byte.
Encryption and signing. crypto.subtle.encrypt() and crypto.subtle.sign() handle the heavier operations: encrypting data with a key, verifying a signature, doing key exchange. This is where you reach when you are implementing a real protocol, not just measuring data.
There is no shortcut for password hashing in Web Crypto. Argon2 and bcrypt are not part of the API. If you need those, you reach for a separate library. Web Crypto is great for general crypto, not specifically for passwords.
What it does not do
A few things people often expect from Web Crypto that it cannot do:
Password hashing. Mentioned above. The closest is PBKDF2, which is acceptable but not great. For real password storage, see hash passwords correctly in 2026.
Old algorithms. MD5 is not in the API. SHA-1 is, but only for legacy signature verification, you cannot compute new SHA-1 hashes. RC4 is not in the API. The browser refuses to weaken your security by exposing broken algorithms.
Encrypted local storage. There is no “save this encrypted in the browser” call. You can encrypt and decrypt manually, but the browser will not manage the key for you long term unless you handle it yourself.
Symmetric block ciphers without authentication. AES-CBC is in the API, but the modern recommendation is AES-GCM, which authenticates the ciphertext on top of encrypting it. Use AES-GCM unless you have a very specific reason not to.
A quick example: hashing a string
async function sha256(str) {
const bytes = new TextEncoder().encode(str);
const hash = await crypto.subtle.digest("SHA-256", bytes);
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
await sha256("hello world");
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
Three lines do real work: encode the string to bytes, hash the bytes, convert the hash bytes to hex.
If you just want a hash of a string without writing any code, the hash generator on AldeaCode does this for you, in your browser, no upload.
Key storage: where the footguns live
Web Crypto generates and uses keys, but it does not give you a permanent place to store them across page loads. You have a few options, each with tradeoffs:
In memory. The key exists for the lifetime of the tab. When the user closes the page, the key is gone. Good for ephemeral protocols (key exchange in a single session). Useless for anything the user expects to persist.
localStorage / IndexedDB. The key persists, but any script on the page can read it. Same problem as storing JWTs in localStorage: an XSS vulnerability becomes a key leak.
Non-extractable keys via IndexedDB. The Web Crypto API can mark a key as “non-extractable” and store it in IndexedDB through crypto.subtle.exportKey() skipping. The key sits in the browser’s secure storage, your code can use it through the API but never read its raw bytes. This is the safest option, but only some operations support it.
Operating system keychain. The browser does not expose the OS keychain to JavaScript. If you need that, you need a native app or a browser extension with elevated permissions.
For most web apps, the right answer is “do not store long lived keys in the browser at all”. Issue them server side, send them down with each session, let them expire when the session ends. The Web Crypto API is then your best friend for using them, just not for keeping them.
When Web Crypto is the right tool
It is the right tool when:
- You need cryptographic randomness (always use it for random ids, tokens, salts).
- You are computing a hash for integrity or fingerprinting.
- You are implementing a protocol that requires encryption or signing on the client side.
- You are checking a server signed value before trusting it.
It is not the right tool when:
- You need to hash passwords (use Argon2 or bcrypt server side).
- You need long lived encrypted local storage (think hard about whether you really do).
- You are tempted to “encrypt the JWT” before sending it (that is what HTTPS is for).
The hash generator, the base64 encoder and the UUID generator on AldeaCode all use Web Crypto under the hood. They are useful when you want the result without writing code, and the data never leaves your tab.
The Web Crypto API is the rare browser feature that just works, is audited, and replaces a category of bug prone library code with a single built in. Use it for what it is good at, route around it for what it is not.