Frontend Labs
⋯

46. The Web Crypto API: hashing, AES-GCM, key derivation, asymmetric crypto

crypto.subtle (SubtleCrypto) is the browser's native, hardware-backed cryptography API — always prefer it over any pure-JS crypto library for primitives it supports, both for correctness (no re-implemented, easy-to- get-wrong primitives) and speed (native code, sometimes hardware-accelerated).

Hashing (one-way, for integrity checks and password-adjacent use — never for storing passwords directly, see PBKDF2 below):

async function sha256(text) {
  const data = new TextEncoder().encode(text);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  return Array.from(new Uint8Array(hashBuffer))
    .map((b) => b.toString(16).padStart(2, "0")).join("");
}
await sha256("hello");  // "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

Symmetric encryption (AES-GCM) — the standard choice for encrypting data you'll decrypt yourself later (client-side vault, encrypted notes, §48):

async function generateKey() {
  return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
}

async function encrypt(key, plaintext) {
  const iv = crypto.getRandomValues(new Uint8Array(12));   // MUST be unique per encryption
  const ciphertext = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext)
  );
  return { iv, ciphertext };   // store BOTH — decryption needs the exact same iv
}

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

Deriving a key from a user password (PBKDF2) — never use a password directly as an encryption key; derive a proper key through many hashing iterations to make brute-forcing expensive:

async function deriveKeyFromPassword(password, salt) {
  const keyMaterial = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]
  );
  return crypto.subtle.deriveKey(
    { name: "PBKDF2", salt, iterations: 600_000, hash: "SHA-256" },  // OWASP 2023+ minimum
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"]
  );
}

Asymmetric crypto (ECDSA for signing, RSA-OAEP/ECDH for key exchange) — for verifying authenticity or exchanging a symmetric key without ever transmitting it:

async function signMessage(privateKey, message) {
  const signature = await crypto.subtle.sign(
    { name: "ECDSA", hash: "SHA-256" }, privateKey, new TextEncoder().encode(message)
  );
  return signature;
}
async function verifySignature(publicKey, signature, message) {
  return crypto.subtle.verify(
    { name: "ECDSA", hash: "SHA-256" }, publicKey, signature, new TextEncoder().encode(message)
  );
}

Generated from real-life-JS-guide.md · 8 Oct 2026 16:36