Reference implementation

SubtleCrypto PBKDF2 and AES-GCM example

This guide shows how to call SubtleCrypto.deriveKey() with PBKDF2-SHA-256 and use the derived key with AES-256-GCM. The same explicit payload can be reproduced in a browser, Node.js and Python.

Open the AES-GCM tool

Browser Web Crypto example

All byte fields are Base64URL. AES-GCM output is split into ciphertext and its final 16-byte authentication tag. HashyTools records PBKDF2-SHA-256 and 600,000 iterations in each new payload instead of relying on hidden defaults.

const bytes = new TextEncoder().encode(plaintext);
const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
const key = await crypto.subtle.deriveKey(
  { name: 'PBKDF2', hash: 'SHA-256', salt, iterations: 600000 }, keyMaterial,
  { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']
);
const encrypted = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData: aad }, key, bytes));
const ciphertext = encrypted.slice(0, -16);
const tag = encrypted.slice(-16);

Node.js compatibility

import { createDecipheriv, pbkdf2Sync } from 'node:crypto';

const key = pbkdf2Sync(password, salt, 600000, 32, 'sha256');
const decipher = createDecipheriv('aes-256-gcm', key, iv);
decipher.setAAD(aad);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([
  decipher.update(ciphertext),
  decipher.final()
]).toString('utf8');

Decode each Base64URL field before passing it to Node. The authentication tag must be set before final().

Python compatibility

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

key = PBKDF2HMAC(hashes.SHA256(), 32, salt, 600000).derive(password.encode())
encrypted = AESGCM(key).encrypt(iv, plaintext.encode(), aad)
ciphertext, tag = encrypted[:-16], encrypted[-16:]

Encode salt, iv, ciphertext, tag and optional aad as Base64URL before writing the JSON fields.

Test before production

Use a fixed password, salt, IV and AAD only in an automated test, then compare the decoded ciphertext and tag byte-for-byte between runtimes. Generate fresh random salt and IV values for actual encryption.

Method and sources

Written and tested by the HashyTools engineering team. Reviewed August 31, 2026. Examples are checked against the published deterministic vector and automated browser tests.