Web Crypto APIs: Secure Encryption in the Browser
The Web Crypto API enables AES encryption, hashing, HMAC, and key generation in browsers. Learn how to use these APIs securely for client-side encryption that protects user data.
Web Crypto APIs: Secure Encryption in the Browser
Encryption used to require server-side systems or complex libraries. The Web Crypto API changed this by providing cryptographic operations directly in browsers. Built into every modern browser, it enables AES encryption, hashing, HMAC, and key generation without external dependencies. Understanding and using these APIs properly is essential for building privacy-preserving applications.
The SubtleCrypto Interface
The Web Crypto API centers on the crypto.subtle interface (or window.crypto.subtle in non-worker contexts). This interface provides cryptographic operations using a standard API across all browsers.
The design reflects security best practices: operations return Promises (enabling non-blocking execution), algorithms must be explicitly specified, and errors surface clearly rather than being silently ignored.
Key concepts:
Algorithms define the cryptographic operations. AES-GCM, AES-CBC, RSA-OAEP, ECDSA—each has specific parameters and security characteristics. The API validates algorithm choices and parameters against security requirements.
Keys are CryptoKey objects containing cryptographic key material. Keys are bound to specific algorithms and can be exported in various formats or stored in the browser's key storage.
Operations are asynchronous. Encryption, decryption, hashing—these are computationally intensive and potentially blocking. The Promise-based API prevents UI freezing during operations.
Understanding AES Encryption
AES (Advanced Encryption Standard) is the gold standard for symmetric encryption. It provides strong security with efficient implementation, making it ideal for browser-based encryption.
AES-GCM (Galois/Counter Mode) is the most commonly recommended mode. It provides both confidentiality (no one can read your data) and authentication (you know who created the data). The API handles this automatically—you get both properties with a single operation.
AES-CBC (Cipher Block Chaining) is older but still widely used. It provides confidentiality only—there's no built-in authentication. If you need authentication with CBC, you must add it yourself (typically with HMAC).
The encryption process:
// Generate a random key
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Encrypt data
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV for GCM
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode('Sensitive data')
);The initialization vector (IV) must be unique for each encryption with the same key. Using the same IV with the same key compromises security. GCM mode uses a 12-byte IV by default; the API generates and handles it correctly if you provide it.
Decryption reverses the process:
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encrypted
);
const text = new TextDecoder().decode(decrypted);The AES encryption tool demonstrates these operations with a usable interface. You can encrypt data in the browser, see the encrypted output, and decrypt it back—all without any server involvement.
Key Generation and Management
Cryptographic security depends on key quality. Weak keys or predictable keys undermine otherwise sound encryption. The Web Crypto API provides secure key generation built on the browser's secure random number generator.
Generating a 256-bit AES key:
const key = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256 // 256-bit key provides strong security
},
true, // extractable - can be exported
['encrypt', 'decrypt']
);The API generates keys from cryptographically secure random numbers. Unlike Math.random(), the browser's cryptographic random is suitable for security applications.
Key export converts CryptoKey objects to portable formats:
const exported = await crypto.subtle.exportKey('raw', key);
const keyBytes = new Uint8Array(exported);
// Store keyBytes somewhere secureKey import converts portable formats back to CryptoKey objects:
const key = await crypto.subtle.importKey(
'raw',
keyBytes,
{ name: 'AES-GCM' },
true,
['encrypt', 'decrypt']
);Export/import enables secure storage. Store the exported key bytes in localStorage, IndexedDB, or a file. When needed, import the key and use it for encryption or decryption.
Hashing with SHA
Hashing produces fixed-size "fingerprints" of data. Given the hash, you can't determine the original data; given the original data, you get the same hash. This is useful for verifying data integrity, storing passwords safely, and creating message digests.
The Web Crypto API supports SHA-1, SHA-256, SHA-384, and SHA-512. Use SHA-256 or stronger—SHA-1 is deprecated for security use.
Hashing data:
const data = new TextEncoder().encode('Message to hash');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashBytes = new Uint8Array(hashBuffer);
// hashBytes contains the 32-byte hashConverting to hex for display:
const hex = Array.from(hashBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('');The hash generator implements SHA-256, SHA-384, and SHA-512 in the browser. Enter text or upload files to see their cryptographic hashes—all processing happens locally.
HMAC for Message Authentication
HMAC (Hash-based Message Authentication Code) provides authentication—verifying that a message came from a specific sender and wasn't modified. Unlike encryption, HMAC produces a verifiable output without hiding the original message.
HMAC uses a secret key and a hash function. The sender computes HMAC(message, key) and sends both. The receiver computes HMAC(message, key) and compares. If the computed value matches the received value, the message is authentic.
// Generate HMAC key
const key = await crypto.subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
true,
['sign', 'verify']
);
// Sign a message
const message = new TextEncoder().encode('Message to authenticate');
const signature = await crypto.subtle.sign(
'HMAC',
key,
message
);
// Verify
const valid = await crypto.subtle.verify(
'HMAC',
key,
signature,
message
);HMAC is useful for:
- API request signing (verify requests come from legitimate sources)
- Data integrity verification (detect tampering)
- Password verification (compare hashes without storing plaintext)
The HMAC generator provides an interactive interface for creating and verifying HMACs. You can generate keys, sign messages, and verify signatures—all in the browser.
Common Mistakes and How to Avoid Them
Using predictable IVs. Never reuse initialization vectors with the same key in AES-CBC or AES-GCM. Always generate random IVs. AES-GCM with a repeated IV exposes ciphertext.
Storing keys in localStorage. localStorage is visible to JavaScript, making it vulnerable to XSS attacks. Use sessionStorage for temporary keys, or encrypt the key storage itself.
Using ECB mode. Electronic Codebook (ECB) mode reveals patterns in encrypted data. Never use ECB for real encryption—it essentially provides no confidentiality.
Skipping authentication. Encryption without authentication allows attackers to modify ciphertext. If they understand the format, they can alter content predictably. Use AES-GCM (which includes authentication) or combine AES-CBC with HMAC.
Hardcoding secrets. Source code gets committed to repositories, shared with contractors, and exposed in logs. Secrets must come from secure storage, not code.
Using weak key derivation. If deriving keys from passwords, use PBKDF2 or Argon2 with strong parameters. Simple hash(password) is not key derivation.
Encryption Patterns for Common Use Cases
Encrypting files for local storage:
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Store key securely, encrypt file with AES-GCM + random IVPassword-based encryption:
const keyMaterial = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);Signing API requests:
const key = await crypto.subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
true,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(requestBody)
);
// Include signature in request headersBrowser Compatibility and Limitations
The Web Crypto API is available in all modern browsers. IE11 has partial support; older browsers may lack it entirely. For production use, check window.crypto or window.crypto.subtle availability.
Limitations to understand:
No RSA encryption for large data. RSA can only encrypt data up to key size minus padding overhead (~250 bytes for 2048-bit keys). For large data, use hybrid encryption: generate an AES key, encrypt data with AES, encrypt AES key with RSA.
Key storage is limited. The IndexedDB-based key storage has size limits and isn't automatically secure. Keys in storage are encrypted with a master key derived from user credentials.
No user interaction for key access. Unlike smartcards or hardware security modules, browser keys are accessible to any JavaScript running in the same origin. XSS vulnerabilities compromise key security.
Export limitations. Keys must be marked extractable at generation time to be exported. Non-extractable keys cannot leave the browser, but this also means you can't back them up or transfer them.
Security Considerations
The Web Crypto API implements cryptographic primitives correctly, but application security depends on how you use these primitives:
Validate all inputs. The API assumes well-formed inputs; malformed data causes errors. Validate before calling crypto operations.
Use constant-time comparison. When verifying HMACs or other values, use crypto.subtle.verify() rather than manual comparison to prevent timing attacks.
Handle errors appropriately. Crypto errors might indicate attacks or implementation bugs. Log errors, don't expose details to users.
Rotate keys. Have a plan for key rotation. If a key is compromised, can you decrypt existing data? Can you migrate to new keys?
Consider the threat model. Web Crypto operates in the browser sandbox—JavaScript has access to keys, network traffic is visible, the user can inspect everything. Against adversaries who control the browser or network, cryptography provides limited protection.
Practical Application: Privacy-Preserving Data Storage
One compelling use case: encrypt data locally, then store in cloud services. The service receives encrypted blobs it cannot read; you retain control over the decryption key.
// Encrypt before upload
const key = await generateOrRetrieveKey(); // Your key, never uploaded
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(plaintext)
);
// Upload encrypted, iv (not the key) to the serviceThe cloud service stores encrypted data. Even if breached, the data remains unreadable without your key. The service can't read your data; government requests to the service yield encrypted blobs.
This pattern applies to notes, documents, photos—any data you'd store in cloud services but want to protect from unauthorized access. You get cloud storage benefits with local encryption security.
Getting Started
Web Crypto APIs are available now in every modern browser. No libraries, no dependencies—just use crypto.subtle directly.
Start with the tools that demonstrate these capabilities:
- AES Encrypt for symmetric encryption and decryption
- Hash Generator for SHA-256, SHA-384, SHA-512 hashing
- HMAC Generator for message authentication
Each tool runs entirely in your browser. Your data never leaves your device. The cryptographic operations are implemented using the same Web Crypto APIs available to your applications.
Understanding these APIs enables building applications that protect user data without server-side encryption infrastructure. Privacy-preserving applications are possible because the browser itself is a capable cryptographic environment.
Try these tools
Encrypt and decrypt text with AES-256-GCM using a passphrase. All processing done securely in your browser.
Generate cryptographic hashes instantly in your browser using SHA-256, SHA-512, SHA-1, or MD5. All processing done locally.
Generate HMAC codes for API authentication and webhook signing. All processing done in your browser.



