Skip to main content
{/}codefunc
Web & APIJuly 18, 2026 · 6 min read

AES in the Browser: Web Crypto Demos vs Production Key Management

Use AES-GCM with Web Crypto for demos and learning, understand IV and auth tags, and know why inventing browser-only crypto protocols fails in production.

By codefunc

aesweb-cryptoencryptionsecuritygcmhmac

AES in the browser is real cryptography: Web Crypto exposes AES-GCM that browsers implement in native code. That does not mean you should invent a "secure notes" protocol entirely in client-side JavaScript, hard-code a key, or assume ciphertext in localStorage is safe from XSS. The gap between a working demo and a production system is almost always key management, not the AES call itself.

Bottom line: Prefer AES-GCM via crypto.subtle for authenticated encryption demos. Generate a random IV per message, never reuse IV+key pairs, and never ship long-term secrets in frontend bundles. For production, use proven protocols (TLS, WebAuthn, vetted libraries, server-side KMS) — do not assemble hash + AES + HMAC from blog posts into a custom scheme.

Who this guide is for

Frontend and full-stack developers exploring Web Crypto, building encrypted-at-rest demos, or reviewing a PR that "just encrypts with AES in the browser." If you need to protect passwords or session tokens, read the threat-model section before copying any snippet.

Confidentiality, integrity, and AES modes

Goal Mechanism
Confidentiality Encryption hides plaintext
Integrity / authenticity Detect tampering; prove who could produce the blob
Both in one step Authenticated encryption (AES-GCM, AES-CCM, ChaCha20-Poly1305)

AES-CBC without a separate MAC is a common footgun: ciphertext can be malleable. AES-GCM provides an authentication tag so decryption fails if the ciphertext (or AAD) was modified. In browser work, default to GCM unless you are implementing a specified protocol that requires another mode.

Hashing (Hash Generator) is not encryption — digests are one-way fingerprints. HMAC (HMAC Generator) authenticates with a key but does not hide plaintext. Use AES when you need confidentiality; use HMAC or GCM tags when you need authenticity.

Web Crypto AES-GCM sketch

Secure contexts only (HTTPS or localhost). Keys should be CryptoKey objects that are non-extractable when you can avoid exporting them.

async function encryptAesGcm(plainText, key) {
  const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV for GCM
  const encoded = new TextEncoder().encode(plainText);
  const cipherBuf = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    encoded,
  );
  return { iv, cipherBuf }; // store/send both
}

async function decryptAesGcm(iv, cipherBuf, key) {
  const plainBuf = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv },
    key,
    cipherBuf,
  );
  return new TextDecoder().decode(plainBuf);
}

Generate keys with crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]) for demos that never leave the tab. For passphrase-based demos, derive keys with PBKDF2 or Argon2 (via WASM) — never use SHA-256(password) as an AES key.

Experiment with round-trips in the AES Encrypt/Decrypt tool — it runs locally so sample plaintexts do not upload to a server. Treat tool output as educational; production systems need audited key storage and protocol design.

IV, nonce, and the reuse rule

GCM requires a unique IV (nonce) for each encryption under a given key. Reusing an IV with the same key can break confidentiality and authenticity guarantees catastrophically.

Rules:

  • Use a 96-bit random IV from crypto.getRandomValues for typical messages (or a counter scheme designed by a cryptographer).
  • Store the IV alongside the ciphertext (IVs are not secret).
  • Rotate keys if you approach astronomical message counts under one key.
  • Never hard-code an IV in source control "for simplicity."

What browser demos get wrong

1. Keys in source or localStorage

Anything shipped to the browser can be read by the user and by XSS. An AES key in your JS bundle encrypts only against people who lack the bundle — which is nobody who can open DevTools. Encrypting localStorage with a key also in localStorage is theater.

2. Inventing protocols

Custom flows like "hash the password, AES-encrypt the JWT, HMAC the result, put it in a cookie" without a threat model create novel failure modes. Use TLS for transport, established auth (OAuth2/OIDC, session cookies with proper flags), and standard APIs for at-rest encryption when a server or KMS holds keys.

3. Ignoring XSS

If your app has XSS, attacker script runs as the user: it can call Web Crypto with keys already in memory, exfiltrate ciphertext and passphrases, and bypass your UI. Encryption does not replace input sanitization, CSP, and safe DOM practices.

4. CBC + separate "checksum"

Rolling AES-CBC and then appending SHA-256 of the plaintext (or ciphertext) is easy to get wrong (order, encoding, timing). GCM exists so you do not assemble this yourself.

5. Sharing one global IV or key across users

Multi-tenant web apps must isolate keys. A single hardcoded AES key for all users means one leak decrypts everything.

When browser AES does make sense

  • Learning and debugging — understand IV + ciphertext + tag layout.
  • Client-side encryption before upload where the server should not see plaintext, and the key comes from the user (passphrase) or from a proper key exchange — E2E patterns used by password managers and secure messengers. These systems still rely on carefully designed protocols, not a single encrypt call.
  • File encryption in a progressive web app with user-held keys and clear UX about recovery (lose the key, lose the data).
  • Short-lived keys created in-page for a session, marked non-extractable, never written to disk.

If the requirement is "users must not read each other's data on the server," you need server-side encryption or true E2E design — not only AES in React state.

Pairing with hashing and HMAC

Tool / API Use
Hash Generator / digest Fingerprints, checksums — not secrecy
HMAC Generator / sign HMAC Authenticity with shared secret
AES Encrypt/Decrypt / AES-GCM Confidentiality + integrity

Do not replace TLS with AES-in-JS between browser and API. TLS already provides a battle-tested transport protocol. Application-level encryption is additive for specific threats (honest-but-curious servers, stolen disks), not a substitute for HTTPS.

Production key management (minimum bar)

  1. Prefer not holding long-term data keys in JS. Decrypt on a server or via a KMS-backed API when the threat model allows.
  2. If E2E is required, use a vetted design (libsignal-style, age, NaCl/libsodium constructions) rather than composing Web Crypto primitives ad hoc.
  3. Derive keys from passphrases with a memory-hard KDF and a per-user salt; never reuse salts.
  4. Plan recovery — password reset that emails a new key is not E2E; be honest in product copy.
  5. Rotate and revoke — log key versions next to ciphertext.
  6. Threat-model XSS and extensions — browser crypto is still inside the user's trust boundary.

Practical takeaway

AES-GCM in Web Crypto is appropriate for demos, user-held-secret features, and carefully scoped E2E designs. It is inappropriate as a drop-in "make this SPA secure" layer with keys in the bundle. Generate random IVs, use authenticated encryption, keep production secrets out of frontend code, and do not invent protocols from stacked primitives. Practice locally with AES Encrypt/Decrypt, and use Hash Generator / HMAC Generator only for the jobs those primitives actually solve.

Sources

Try it on codefunc

Related articles

3

Search tools

Find a developer tool