|$ curl https://forge-ai.dev/api/markdown?path=docs/js/web-crypto
$cat docs/web-crypto-api.md
updated Last week·30 min read·published

Web Crypto API

JavaScriptSecurityCryptographyIntermediate to Advanced🎯Free Tools
Introduction

The Web Crypto API provides a standardized interface for performing cryptographic operations in JavaScript. It is accessible via window.crypto.subtle (the SubtleCrypto interface) and supports hashing, encryption, decryption, key generation, digital signatures, and key derivation. The API is designed to be secure, performant, and portable across browsers and Node.js.

Unlike third-party crypto libraries, the Web Crypto API is built into the browser and backed by native implementations (often hardware-accelerated). All operations are asynchronous and return Promises, making them compatible with async/await patterns. The API is available in secure contexts (HTTPS) and in Web Workers.

web-crypto-intro.js
JavaScript
1// Web Crypto API overview
2const crypto = window.crypto || globalThis.crypto;
3const subtle = crypto.subtle;
4
5// Available operations:
6// - digest: hash functions (SHA-1, SHA-256, SHA-384, SHA-512)
7// - encrypt/decrypt: symmetric encryption (AES-GCM, AES-CBC)
8// - generateKey: create cryptographic keys
9// - sign/verify: digital signatures (HMAC, RSASSA-PKCS1-v1_5)
10// - deriveKey/deriveBits: key derivation (PBKDF2, ECDH)
11// - importKey/exportKey: serialize keys
12
13// All methods return Promises
14const hash = await subtle.digest("SHA-256", new TextEncoder().encode("hello"));
15console.log(new Uint8Array(hash));
Hashing with digest()

The digest() method computes a cryptographic hash of the input data. It supports SHA-1 (deprecated for security), SHA-256, SHA-384, and SHA-512. The output is an ArrayBuffer containing the raw hash bytes. Hashing is one-way — you cannot reverse a hash to recover the original input.

web-crypto-hash.js
JavaScript
1// SHA-256 hash
2async function sha256(message) {
3 const encoder = new TextEncoder();
4 const data = encoder.encode(message);
5 const hashBuffer = await crypto.subtle.digest("SHA-256", data);
6 const hashArray = Array.from(new Uint8Array(hashBuffer));
7 const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
8 return hashHex;
9}
10
11const hash = await sha256("Hello, World!");
12console.log(hash);
13// "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
14
15// SHA-512 hash
16async function sha512(message) {
17 const data = new TextEncoder().encode(message);
18 const hashBuffer = await crypto.subtle.digest("SHA-512", data);
19 return Array.from(new Uint8Array(hashBuffer))
20 .map(b => b.toString(16).padStart(2, "0"))
21 .join("");
22}
23
24// Hash binary data
25async function hashFile(file) {
26 const buffer = await file.arrayBuffer();
27 const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
28 return Array.from(new Uint8Array(hashBuffer))
29 .map(b => b.toString(16).padStart(2, "0"))
30 .join("");
31}

info

crypto.subtle.digest() is synchronous for small inputs but the API is Promise-based for consistency. For file hashing, always use arrayBuffer() to get the raw bytes, then pass them directly to digest().
Encryption & Decryption (AES-GCM)

AES-GCM (Advanced Encryption Standard in Galois/Counter Mode) is an authenticated encryption algorithm. It provides both confidentiality (encryption) and integrity (authentication tag). This means if the ciphertext is tampered with, decryption will fail — providing tamper detection.

web-crypto-aes.js
JavaScript
1// AES-GCM encryption and decryption
2async function encryptAESGCM(key, plaintext, iv) {
3 const encoder = new TextEncoder();
4 const data = encoder.encode(plaintext);
5
6 const encrypted = await crypto.subtle.encrypt(
7 { name: "AES-GCM", iv },
8 key,
9 data
10 );
11
12 return new Uint8Array(encrypted);
13}
14
15async function decryptAESGCM(key, ciphertext, iv) {
16 const decrypted = await crypto.subtle.decrypt(
17 { name: "AES-GCM", iv },
18 key,
19 ciphertext
20 );
21
22 return new TextDecoder().decode(decrypted);
23}
24
25// Generate a random AES-GCM key
26async function generateAESKey() {
27 return await crypto.subtle.generateKey(
28 { name: "AES-GCM", length: 256 },
29 true, // extractable
30 ["encrypt", "decrypt"]
31 );
32}
33
34// Generate random IV (12 bytes recommended for AES-GCM)
35function generateIV() {
36 return crypto.getRandomValues(new Uint8Array(12));
37}
38
39// Full example
40const key = await generateAESKey();
41const iv = generateIV();
42
43const ciphertext = await encryptAESGCM(key, "Secret message!", iv);
44console.log("Encrypted:", ciphertext);
45
46const plaintext = await decryptAESGCM(key, ciphertext, iv);
47console.log("Decrypted:", plaintext); // "Secret message!"

warning

Never reuse an IV (Initialization Vector) with the same key. Reusing IVs with AES-GCM leaks information about the plaintext. Always generate a random IV for each encryption operation and prepend it to the ciphertext if you need to store or transmit them together.
Key Generation

The Web Crypto API supports generating cryptographic keys for various algorithms. Keys can be symmetric (AES, HMAC) or asymmetric (RSA, ECDSA). Generated keys can be used directly for encryption/signing or exported for storage.

web-crypto-keys.js
JavaScript
1// Generate AES key
2const aesKey = await crypto.subtle.generateKey(
3 { name: "AES-GCM", length: 256 },
4 true, // extractable — can be exported
5 ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
6);
7
8// Generate HMAC key
9const hmacKey = await crypto.subtle.generateKey(
10 { name: "HMAC", hash: "SHA-256" },
11 true,
12 ["sign", "verify"]
13);
14
15// Generate ECDSA key pair (asymmetric)
16const ecKeyPair = await crypto.subtle.generateKey(
17 {
18 name: "ECDSA",
19 namedCurve: "P-256"
20 },
21 true, // extractable
22 ["sign", "verify"]
23);
24
25// Export key to raw format
26const rawKey = await crypto.subtle.exportKey("raw", aesKey);
27console.log("Raw key:", new Uint8Array(rawKey));
28
29// Export key as JWK (JSON Web Key)
30const jwk = await crypto.subtle.exportKey("jwk", aesKey);
31console.log("JWK:", jwk);
32
33// Import key from raw format
34const importedKey = await crypto.subtle.importKey(
35 "raw",
36 rawKey,
37 { name: "AES-GCM", length: 256 },
38 true,
39 ["encrypt", "decrypt"]
40);
HMAC Signing

HMAC (Hash-based Message Authentication Code) provides both integrity and authenticity verification. It uses a secret key combined with a hash function to produce a signature. The signature can be verified by anyone with the key, but cannot be forged without it.

web-crypto-hmac.js
JavaScript
1// HMAC signing and verification
2async function signHMAC(key, message) {
3 const encoder = new TextEncoder();
4 const data = encoder.encode(message);
5
6 const signature = await crypto.subtle.sign(
7 "HMAC",
8 key,
9 data
10 );
11
12 return new Uint8Array(signature);
13}
14
15async function verifyHMAC(key, message, signature) {
16 const encoder = new TextEncoder();
17 const data = encoder.encode(message);
18
19 const valid = await crypto.subtle.verify(
20 "HMAC",
21 key,
22 signature,
23 data
24 );
25
26 return valid;
27}
28
29// Generate HMAC key
30const hmacKey = await crypto.subtle.generateKey(
31 { name: "HMAC", hash: "SHA-256" },
32 false, // not extractable (more secure)
33 ["sign", "verify"]
34);
35
36// Sign a message
37const message = "Important: Transfer $1000";
38const signature = await signHMAC(hmacKey, message);
39console.log("Signature:", Array.from(signature).map(b =>
40 b.toString(16).padStart(2, "0")
41).join(""));
42
43// Verify the message
44const isValid = await verifyHMAC(hmacKey, message, signature);
45console.log("Valid:", isValid); // true
46
47// Tampered message fails verification
48const tampered = await verifyHMAC(hmacKey, "Important: Transfer $9999", signature);
49console.log("Tampered:", tampered); // false

info

Use HMAC for API request signing, JWT tokens, and message authentication. HMAC is symmetric — both the signer and verifier need the same key. For asymmetric signing (public/private key pairs), use RSASSA-PKCS1-v1_5 or ECDSA instead.
PBKDF2 Password Hashing

PBKDF2 (Password-Based Key Derivation Function 2) derives a cryptographic key from a password. It applies a pseudorandom function (like HMAC) combined with a salt and iterates thousands of times to slow down brute-force attacks. It is the standard way to derive encryption keys from user passwords.

web-crypto-pbkdf2.js
JavaScript
1// PBKDF2 key derivation
2async function deriveKeyFromPassword(password, salt) {
3 const encoder = new TextEncoder();
4
5 // Import password as raw key material
6 const passwordKey = await crypto.subtle.importKey(
7 "raw",
8 encoder.encode(password),
9 "PBKDF2",
10 false,
11 ["deriveBits", "deriveKey"]
12 );
13
14 // Derive a 256-bit AES-GCM key
15 const derivedKey = await crypto.subtle.deriveKey(
16 {
17 name: "PBKDF2",
18 salt,
19 iterations: 600000, // OWASP recommendation (2023+)
20 hash: "SHA-256"
21 },
22 passwordKey,
23 { name: "AES-GCM", length: 256 },
24 false, // not extractable
25 ["encrypt", "decrypt"]
26 );
27
28 return derivedKey;
29}
30
31// Usage: encrypt data with password
32const password = "user-password-123";
33const salt = crypto.getRandomValues(new Uint8Array(16));
34
35const key = await deriveKeyFromPassword(password, salt);
36
37// Encrypt
38const iv = crypto.getRandomValues(new Uint8Array(12));
39const ciphertext = await crypto.subtle.encrypt(
40 { name: "AES-GCM", iv },
41 key,
42 new TextEncoder().encode("Sensitive data")
43);
44
45// Decrypt (with same password and salt)
46const key2 = await deriveKeyFromPassword(password, salt);
47const plaintext = await crypto.subtle.decrypt(
48 { name: "AES-GCM", iv },
49 key2,
50 ciphertext
51);
52
53console.log(new TextDecoder().decode(plaintext)); // "Sensitive data"

danger

Never use PBKDF2 alone for password storage (like login systems). Use specialized password hashing functions like Argon2id, bcrypt, or scrypt which are designed for password hashing and include memory-hardness features. PBKDF2 is suitable for deriving encryption keys from passwords, not for storing password hashes.
Best Practices
Always use HTTPS — Web Crypto API is only available in secure contexts
Never reuse IVs with AES-GCM — generate a random IV for each encryption operation
Use SHA-256 or stronger for hashing — avoid SHA-1 for security purposes
Use at least 600,000 iterations for PBKDF2 — follow OWASP guidelines
Store salts alongside ciphertext — they are not secret and are needed for decryption
Never store encryption keys in JavaScript — use key wrapping or server-side key management
Use non-extractable keys when possible — prevents key material from being exported
Always verify HMAC signatures before processing signed messages
$Blueprint — Engineering Documentation·Section ID: JS-CRYPTO·Revision: 1.0