Web Crypto AES-GCM: Browser Encryption Guide

Encryption# AES-GCM# Web Crypto API# JavaScript# Browser
Written by HSM Kit Editorial TeamTechnically reviewed by HSM Kit Security Review TeamLast reviewed: September 22, 20265 min read
Need to calculate this now?
Use our free online AES Encryption Tool tool.

The Web Crypto API provides browser-native cryptographic primitives through crypto.subtle. AES-GCM is the preferred general-purpose symmetric mode in Web Crypto because it combines encryption and integrity protection. Correct use still requires unique nonces, deliberate key handling, and a stable ciphertext format.

When Browser Encryption Helps

Web Crypto is useful when encryption must happen before data leaves the browser, for example:

  • encrypting an export before download;
  • protecting locally stored application data;
  • implementing end-to-end encryption where the server never receives plaintext;
  • interoperating with a service that already defines an AES-GCM envelope.

Browser encryption does not protect data from malicious script running in the same page. Cross-site scripting, compromised dependencies, or a malicious browser extension may access plaintext before encryption or after decryption. Content Security Policy, dependency control, and application security remain essential.

Generate an AES-GCM Key

Generate keys with Web Crypto rather than Math.random() or hand-built random strings:

const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt'],
);

The extractable argument is true above so the key can be exported for demonstration. Set it to false when the key should remain a non-exportable CryptoKey in the current environment.

AES-128 and AES-256 are both secure choices when used correctly. Key storage and nonce uniqueness usually matter more than selecting the larger key size.

Encrypt UTF-8 Text

Web Crypto accepts bytes, not JavaScript strings. Encode text with TextEncoder and generate a fresh 12-byte nonce:

const encoder = new TextEncoder();
const nonce = crypto.getRandomValues(new Uint8Array(12));
const plaintext = encoder.encode('Sensitive test message');
const aad = encoder.encode('hsmkit:v1:message');

const encrypted = await crypto.subtle.encrypt(
  {
    name: 'AES-GCM',
    iv: nonce,
    additionalData: aad,
    tagLength: 128,
  },
  key,
  plaintext,
);

const ciphertextAndTag = new Uint8Array(encrypted);

In Web Crypto, the returned buffer contains ciphertext followed by the authentication tag. Other libraries may expose ciphertext and tag as separate fields, so define the interchange format explicitly.

Decrypt and Authenticate

Decryption must receive the same key, nonce, AAD, and tag length:

const decrypted = await crypto.subtle.decrypt(
  {
    name: 'AES-GCM',
    iv: nonce,
    additionalData: aad,
    tagLength: 128,
  },
  key,
  ciphertextAndTag,
);

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

If the ciphertext, tag, nonce, AAD, or key is wrong, the promise rejects. Treat rejection as a single authentication failure. Do not use unauthenticated plaintext or attempt to ignore a failed tag.

Package the Ciphertext

Store enough metadata to decrypt the record later. A compact JSON envelope might look like:

{
  "v": 1,
  "alg": "A256GCM",
  "kid": "2026-09-primary",
  "iv": "base64url nonce",
  "ct": "base64url ciphertext plus tag"
}

The algorithm and version are public. The nonce is public. Only the key must remain secret. Encode binary fields with Base64url or Base64 according to a documented rule; never convert arbitrary ciphertext bytes directly to a JavaScript string.

Bind fields such as version, record ID, tenant ID, or content type through additionalData when they must not be changed. AAD is authenticated but not encrypted, so do not place secrets in it.

Nonce Management

A 12-byte nonce is recommended for GCM. Generate a new nonce for every encryption under a key:

const nonce = crypto.getRandomValues(new Uint8Array(12));

Never reuse the nonce when encrypting different plaintext with the same key. Retries, multiple tabs, service workers, restored application state, and shared accounts all need consideration. For high-volume systems, design a durable nonce allocation scheme rather than assuming random generation solves coordination forever.

Read AES IV and nonce reuse before designing shared or long-lived keys.

Export and Import Keys

Export a generated key as raw bytes only when the application has a secure wrapping or storage plan:

const rawKey = await crypto.subtle.exportKey('raw', key);

const importedKey = await crypto.subtle.importKey(
  'raw',
  rawKey,
  { name: 'AES-GCM' },
  false,
  ['encrypt', 'decrypt'],
);

Do not store raw AES keys in localStorage. Any script running on the origin can read localStorage. IndexedDB can store non-exportable CryptoKey objects in supported browsers, but same-origin malicious script may still use the key. For strong user authentication or cross-device recovery, use a reviewed key-management design.

Passwords Are Not AES Keys

A password must be processed by a password-based key derivation function (KDF). Do not UTF-8 encode a password, truncate it to 16 or 32 bytes, or hash it once and call the result a key.

Web Crypto supports PBKDF2. A password-based envelope must store a random salt and KDF parameters alongside the nonce and ciphertext. Choose iteration counts based on current platform measurements and policy. For new designs requiring memory-hard derivation, a reviewed Argon2 implementation may be more appropriate, although it is not currently a native Web Crypto algorithm.

Common Errors

ErrorWhy it failsCorrect approach
Reusing a fixed nonceBreaks GCM securityGenerate or allocate a unique 12-byte nonce
Using Math.random()Not cryptographically secureUse crypto.getRandomValues()
Omitting envelope versionPrevents safe migrationStore explicit version and algorithm
Storing raw key in localStorageReadable by same-origin scriptUse non-exportable keys or external key management
Losing AADDecryption authentication failsDefine and persist AAD inputs
Splitting the Web Crypto output incorrectlyDrops or corrupts the tagDocument ciphertext/tag layout
Encrypting without XSS controlsMalicious script can steal plaintextHarden the application and CSP

Interoperability Notes

Before exchanging AES-GCM data with Java, .NET, OpenSSL, Node.js, or another service, agree on:

  • key length and raw key bytes;
  • 12-byte nonce representation;
  • 128-bit tag length;
  • whether ciphertext and tag are combined;
  • exact AAD bytes;
  • Base64 versus Base64url and padding rules;
  • envelope version and byte order.

Use known test vectors before production integration. The AES Encryption Tool can help inspect encodings with non-sensitive data. For the mode decision itself, see AES-GCM vs AES-CBC.

Standards & references

Technical content is reviewed against the cited public standards and official documentation. Confirm licensed standards and vendor documentation before production use.
Related Tool
AES Encryption Tool
← Previous
PKCS#7 Padding for AES: How It Works