aes-256cryptographyjavascriptweb-crypto

How to Encrypt Text with AES-256-GCM in the Browser

Learn to encrypt and decrypt text directly in the browser with AES-256-GCM and PBKDF2 using the native Web Crypto API. No external libraries, 100% private.

May 4, 2026·7 min read

AES-256-GCM is the dominant symmetric encryption standard in modern applications. What many developers don't realize is that you don't need any external library to use it — the browser itself exposes the Web Crypto API, a native, audited, high-performance implementation of the most important cryptographic algorithms.

This article walks through the complete process — from password to ciphertext — with real JavaScript code and explanations of why each part is necessary.

What does AES-256-GCM mean?

AES (Advanced Encryption Standard) is the most widely used symmetric encryption algorithm in the world. Symmetric means the same key encrypts and decrypts. It's the algorithm protecting your HTTPS connections, your encrypted hard drive and most banking applications.

256 indicates the key length in bits. Longer keys mean stronger security. AES-128 is perfectly secure for general use, but AES-256 is the standard for sensitive data and is recommended by agencies like NIST and NSA for classified information.

GCM (Galois/Counter Mode) is the cipher's mode of operation. It's especially valuable because it does two things simultaneously:

  • Encryption: converts the plaintext into an unreadable block without the key
  • Authentication: generates an authentication tag that detects any manipulation of the ciphertext

This combination is called Authenticated Encryption with Associated Data (AEAD). If someone modifies the ciphertext in transit, the decryption operation will fail with an error rather than silently returning corrupted data.

The problem: password ≠ key

A password like "my-secure-password-2026" is arbitrary text of variable length. AES-256 needs exactly 256 bits (32 bytes) generated in a cryptographically secure way. That's where PBKDF2 (Password-Based Key Derivation Function 2) comes in.

PBKDF2 takes your password and a random salt and applies SHA-256 thousands of times to produce a key of exact length. The iterations (100,000 in our example) make the process computationally expensive, slowing brute-force attacks even if the attacker has the ciphertext.

Complete JavaScript implementation

1. Derive the key with PBKDF2

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

2. Encrypt

async function encrypt(plaintext, password) {
  const enc  = new TextEncoder();
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const iv   = crypto.getRandomValues(new Uint8Array(12));
  const key  = await deriveKey(password, salt);

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

  // Pack salt + iv + ciphertext into a single Base64 buffer
  const buf = new Uint8Array(28 + ciphertext.byteLength);
  buf.set(salt, 0);
  buf.set(iv, 16);
  buf.set(new Uint8Array(ciphertext), 28);
  return btoa(String.fromCharCode(...buf));
}

The result is a Base64 string that includes the salt (16 bytes) and IV (12 bytes). No external state needs to be stored alongside the ciphertext.

3. Decrypt

async function decrypt(b64, password) {
  const buf  = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
  const salt = buf.slice(0, 16);
  const iv   = buf.slice(16, 28);
  const data = buf.slice(28);
  const key  = await deriveKey(password, salt);

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

If the password is wrong or the ciphertext has been modified, crypto.subtle.decrypt will throw a DOMException. Always handle that case with try/catch.

Why this pattern is secure

Random salt per operation: even if you encrypt the same text with the same password twice, the output is always different. Prevents rainbow table and precomputation attacks.

Fresh IV each encryption: the IV (Initialization Vector) ensures that the same plaintext plus the same key never produces the same ciphertext. Reusing an IV with the same key is a critical vulnerability that completely breaks the cipher's confidentiality.

GCM authentication tag: the last 16 bytes of the ciphertext are the authentication tag. If you modify any bit of the ciphertext, salt or IV, decryption fails with an error. Impossible to tamper with the message without the receiver detecting it.

Common mistakes to avoid

Don't store the derived key in localStorage. If your application has an XSS vulnerability, the key is exposed to any malicious script. Always derive the key from the password at the moment of use and discard it immediately after.

Don't use AES-ECB. It's the basic AES mode without IV or authentication. Repeated patterns in the plaintext produce repeated patterns in the ciphertext, leaking information about the original content. Never use ECB for real data.

Don't truncate the IV. A 12-byte IV is the standard size for AES-GCM and is the size that optimizes the internal authentication algorithm. Using a different size adds complexity with no benefit.

Web Crypto API: native, zero dependencies

All the code above works in any modern browser and also in Node.js 18+, Deno and Bun via the same global crypto.subtle object. No npm install, no third-party library vulnerabilities, no dependencies to maintain.

It's the same cryptographic implementation your operating system uses, audited by the security teams of browser manufacturers. There's no reason to use libraries like crypto-js in new projects.

Try it without writing code

If you want to encrypt text or files right now without setting anything up, our online encryptor implements exactly this algorithm: AES-256-GCM with PBKDF2, running entirely in your browser. Your data never leaves your device.

Try it without code

AES Text Encryptor

AES-256-GCM + PBKDF2 in browser.

Open tool

Built by

Miguel Ángel Colorado Marin

Full-Stack Developer · Guadalajara, España

I develop web apps, digital tools and full projects — from design to deployment.

Contact me