Cryptography

How to Encrypt Text in the Browser Using the Web Crypto API

9 min read
By
How to Encrypt Text in the Browser Using the Web Crypto API

Photo by Pixabay from Pexels

For a long time, if you wanted to encrypt something in a web application, you had two choices. You could send the plaintext to a server and let the server encrypt it, which meant the server saw the plaintext. Or you could use a JavaScript library like CryptoJS, which was slow and had a history of implementation bugs.

Neither option was great. Then the Web Crypto API came along and changed everything.

What the Web Crypto API Is

The Web Crypto API is a native browser API for cryptographic operations. It is available in every modern browser through window.crypto.subtle. The "subtle" name is a warning: the API gives you low-level access to cryptographic primitives, and using them correctly requires understanding what you are doing. But the operations themselves are implemented natively by the browser, not in JavaScript, which means they are fast, constant-time, and audited.

The API supports the operations you actually need: key generation, key derivation (PBKDF2), encryption and decryption (AES-GCM, AES-CBC), hashing (SHA-256, SHA-384, SHA-512), signing and verification (HMAC, ECDSA, RSA), and key wrapping. For encrypting text, the combination you want is PBKDF2 for key derivation and AES-GCM for encryption.

Why Browser-Side Beats Server-Side

The traditional approach to encryption in web apps is to send the plaintext to the server over HTTPS and let the server encrypt it before storage. This works, but it has a fundamental problem: the server sees the plaintext. Even if the server encrypts it immediately, the plaintext exists in the server's memory, in the request body, in logs, in monitoring systems, and potentially in backups.

Browser-side encryption flips this model. The user enters their text and password in the browser. The browser derives a key from the password using PBKDF2, encrypts the text using AES-GCM, and sends only the ciphertext to the server. The server never sees the plaintext or the key. Even if the server is compromised, the attacker only has ciphertext.

This is the model we use for our AES text encryptor. Everything happens in your browser. Your text and password never leave your device. The server has no way to decrypt your data because it never receives the key.

The Key Derivation Step

Before you can encrypt, you need a key. Users do not have 256-bit random keys memorized. They have passwords. So the first step is turning a password into a key.

This is what PBKDF2 does. You provide a password, a salt (random bytes that make the derivation unique), and an iteration count (how many times to hash). PBKDF2 hashes the password with the salt, over and over, for the specified number of iterations, and produces a key of the desired length.

The iteration count is critical. A single hash would be instant to compute, which means an attacker could try billions of password guesses per second. With 100,000 iterations, each guess takes 100,000 times longer. This turns a crackable password into a safe one, as long as the password is not trivially weak.

Here is what the code looks like:

javascript // Derive a key from a password const enc = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey'] );

const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] );

The salt should be random and stored alongside the ciphertext. The iterations should be at least 100,000 (we use more in our tool). The key is marked as non-extractable, which means JavaScript cannot read the raw key bytes, adding a layer of protection against malicious scripts.

The Encryption Step

Once you have the key, encryption is straightforward:

javascript // Generate a random IV const iv = crypto.getRandomValues(new Uint8Array(12));

// Encrypt the text const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, enc.encode(plaintext) );

The encrypt function takes the algorithm (AES-GCM with the IV), the key, and the plaintext (encoded as bytes). It returns the ciphertext with the authentication tag appended. The IV is not secret and should be stored alongside the ciphertext.

The Decryption Step

To decrypt, you need the same key, the same IV, and the ciphertext:

javascript const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, ciphertext );

const plaintext = new TextDecoder().decode(decrypted);

If the authentication tag does not match (meaning the ciphertext was modified or the wrong key was used), decrypt throws an error. This is GCM's built-in integrity protection at work. You never need to implement a separate MAC check.

Why This Is Better Than CryptoJS

Before the Web Crypto API, the standard way to encrypt in the browser was CryptoJS or similar libraries. These were pure JavaScript implementations of AES. They worked, but they had significant drawbacks:

They were slow. JavaScript is not good at the kind of bit manipulation that AES requires. Native implementations are orders of magnitude faster.

They were harder to audit. A JavaScript crypto library is thousands of lines of code that could contain subtle bugs. The Web Crypto API is implemented by the browser vendor and audited at the platform level.

They did not have access to a secure random number generator. CryptoJS had to fall back to Math.random for IV generation, which is not cryptographically secure. The Web Crypto API uses crypto.getRandomValues, which is.

They could not protect keys. In CryptoJS, the key is a JavaScript object that any script on the page can read. With the Web Crypto API, keys can be marked non-extractable, meaning the raw key bytes are never accessible to JavaScript.

The One Thing to Watch Out For

The Web Crypto API is secure, but it operates within the browser environment. If a malicious browser extension or a compromised script on the page can intercept your password before it reaches the crypto API, no amount of encryption will help. This is why you should only use encryption tools on pages you trust, and why tools like ours run entirely client-side with no external dependencies.

For most users and most use cases, the Web Crypto API is more than secure enough. It gives you native, hardware-accelerated AES-256-GCM encryption with no libraries and no server round-trips. Your text stays on your device, and the server never sees it.

You can see all of this in action with our AES text encryptor. Type a message, choose a password, and watch the ciphertext appear. Everything happens in your browser. For a deeper look at the encryption mode being used, read our AES-256-GCM explained post.

Frequently Asked Questions

Is the Web Crypto API available in all browsers?

Yes, the Web Crypto API (window.crypto.subtle) is available in all modern browsers including Chrome, Firefox, Safari, Edge, and mobile browsers. It has been supported since around 2017. The only exception is very old browsers like Internet Explorer, which is no longer supported by most websites anyway.

Is browser-side encryption secure? Can the browser steal my key?

The browser itself does not have access to your key in a way that would compromise it. The key is managed by the browser crypto subsystem and is not exposed to JavaScript or extensions in plaintext form when using non-extractable keys. The main risk is malicious browser extensions or compromised JavaScript on the page, which is why you should only use encryption tools on pages you trust.

Do I need a library like CryptoJS for browser encryption?

No. The Web Crypto API provides native, hardware-accelerated encryption that is faster and more secure than JavaScript libraries like CryptoJS. Libraries were necessary before the Web Crypto API existed, but there is no reason to use them for AES-GCM, PBKDF2, or SHA-256 in modern browsers.

What is PBKDF2 and why do I need it for browser encryption?

PBKDF2 (Password-Based Key Derivation Function 2) turns a human-readable password into a cryptographic key of the right size (256 bits for AES-256). It does this by hashing the password many times (typically 100,000+ iterations) with a salt, which makes brute-force password guessing computationally expensive. Without PBKDF2, you would need to use a raw key, which is impractical for user-facing tools.

Try NovelCrypt Tools

Experience military-grade encryption for your sensitive data. Create self-destructing messages, encrypt files, or explore our experimental lab tools.

Explore NovelCrypt