JavaScript Web Crypto Table

PieceWhat it doesField note
crypto.getRandomValues(arr)Random bytesThe ONLY randomness for tokens/ids - Math.random is predictable
subtle.digest('SHA-256', data)HashIntegrity and fingerprints - NOT password storage
subtle.generateKeyMake keysNon-extractable by option - keys that cannot leave the browser
subtle.encrypt AES-GCMSymmetric encryptGCM includes auth - the modern default for payload encryption
subtle.sign / verifySignaturesHMAC or ECDSA - verify server-side with the public half
subtle.deriveBits PBKDF2Password to keyHigh iteration count - passwords are low-entropy by nature
Secure context onlyHTTPS gateLike clipboard and camera - subtle is undefined on plain HTTP
Async by designEverything returns promisesKeys may live in secure hardware - sync access would leak them
Reference: the MDN Web Crypto reference. Web Crypto splits in two: crypto.getRandomValues for random BYTES (the only acceptable source for tokens, ids and nonces - Math.random is predictable by design) and crypto.subtle for real cryptography (digest, AES-GCM, signatures, key derivation), which exists ONLY in secure contexts. Two laws outrank the API surface: never store passwords with digest (use deriveBits with PBKDF2 at high iteration counts - hashing is not password storage), and never hand-roll protocol shapes with these primitives - the API provides the bricks; the blueprints are still where the mistakes live. Related tools: typed arrays table (getRandomValues fills typed arrays), storage table (where tokens end up - treat accordingly), and security headers table (the transport half of the story).

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

  1. Generate identifiers safely: const bytes = crypto.getRandomValues(new Uint8Array(16)) then hex/base64 encode - never Math.random for anything an attacker could guess.
  2. Hash for integrity: await crypto.subtle.digest('SHA-256', data) - dedupe, cache keys, download verification; for PASSWORDS use PBKDF2 derivation instead.
  3. 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.

Related tools