JavaScript Web Crypto Table
| Piece | What it does | Field note |
|---|---|---|
crypto.getRandomValues(arr) | Random bytes | The ONLY randomness for tokens/ids - Math.random is predictable |
subtle.digest('SHA-256', data) | Hash | Integrity and fingerprints - NOT password storage |
subtle.generateKey | Make keys | Non-extractable by option - keys that cannot leave the browser |
subtle.encrypt AES-GCM | Symmetric encrypt | GCM includes auth - the modern default for payload encryption |
subtle.sign / verify | Signatures | HMAC or ECDSA - verify server-side with the public half |
subtle.deriveBits PBKDF2 | Password to key | High iteration count - passwords are low-entropy by nature |
Secure context only | HTTPS gate | Like clipboard and camera - subtle is undefined on plain HTTP |
Async by design | Everything returns promises | Keys may live in secure hardware - sync access would leak them |
Web Crypto splits in two: crypto.getRandomValues fills typed arrays with unpredictable bytes (the only acceptable randomness for tokens, ids and nonces - Math.random is predictable by design), and crypto.subtle provides real cryptography: digest, AES-GCM, signatures and key derivation.
Bottom line: two laws outrank the API surface. Hashing is not password storage - passwords go through deriveBits with PBKDF2 at high iteration counts, because their entropy is low by nature. And the API provides bricks, not blueprints: AES-GCM with a reused nonce or a homegrown protocol shape fails in ways the primitives cannot prevent.
The honest part: subtle exists only in secure contexts (HTTPS - localhost counts) and everything returns promises - partly because keys may live in non-extractable hardware-backed storage, where synchronous access would be a security hole wearing an ergonomic hat.
How to use
- Generate identifiers safely: const bytes = crypto.getRandomValues(new Uint8Array(16)) then hex/base64 encode - never Math.random for anything an attacker could guess.
- Hash for integrity: await crypto.subtle.digest('SHA-256', data) - dedupe, cache keys, download verification; for PASSWORDS use PBKDF2 derivation instead.
- Encrypt payloads with AES-GCM: generateKey + encrypt both return promises; GCM's built-in authentication tag means tampered ciphertext fails decryption loudly.
Frequently asked questions
Why is Math.random unacceptable for tokens and ids?
It is a pseudo-random generator optimized for speed, seeded predictably and lacking any entropy guarantee: its outputs are reproducible by anyone who can infer the internal state, which is feasible from observing a handful of consecutive values. Session tokens, password-reset links and API nonces need unpredictability as their security property - so they require a CSPRNG, which is exactly what crypto.getRandomValues provides (backed by the OS entropy pool). The distinction to teach: randomness for simulations and games can be Math.random; randomness that stands between an attacker and an account cannot - and the cost of switching is one line.
Why is SHA-256 hashing not password storage?
Because hashes are fast, and fast is the enemy. A database leak lets an attacker run billions of SHA-256 guesses per second on GPUs - every common password falls immediately. Password storage needs DELIBERATE SLOWNESS plus a unique salt per user, which is key derivation: PBKDF2 (or Argon2id server-side) with hundreds of thousands of iterations makes each guess expensive at scale. In the browser the pattern is deriveBits with PBKDF2 parameters (high iterations, random salt) - and the deeper truth remains: client-side hashing protects nothing on its own (the server sees whatever the client sends); it exists to avoid transmitting the raw password, not to replace server-side storage discipline.
What makes AES-GCM the default choice for payload encryption?
It encrypts AND authenticates in one operation. Plain block modes encrypt but say nothing about tampering - an attacker can flip ciphertext bits and the decryption 'succeeds' with corrupted plaintext (and in some protocols, leaks). GCM appends an authentication tag: decryption with any modified ciphertext or wrong nonce FAILS loudly instead of returning lies. The one famous footgun is nonce reuse - repeating a nonce under the same key breaks GCM's guarantees catastrophically - so the discipline is a fresh random or counter nonce per encryption, stored alongside the ciphertext. For browser-native symmetric crypto, GCM plus getRandomValues nonces is the modern default.
Why is the whole API promise-based and what are non-extractable keys?
Because keys increasingly do not live where JavaScript can read them. The async design lets implementations back subtle operations with secure hardware (TPM, key store) or worker pools; a synchronous API would either block or force keys into readable memory. Non-extractable keys (generateKey with extractable: false) take this further: the key material can NEVER be exported - the private half performs sign/decrypt operations inside the boundary and nothing, neither XSS nor a copy-paste, can walk off with it. The trade is real: non-extractable keys cannot move between browsers or devices, so they fit session-scoped and device-bound crypto (WebAuthn-adjacent patterns), not backup or migration flows.