Encryption Documentation

Learn how our AES-256-GCM encryption works. Complete documentation with code examples for implementing client-side encryption.

How It Works

Key Derivation

Your password is used to derive a 256-bit AES key using PBKDF2 with SHA-256 and 600,000 iterations. This makes brute-force attacks computationally expensive.

Random Values

A random 128-bit salt and 96-bit IV (Initialization Vector) are generated using cryptographically secure random number generation for each encryption.

AES-256-GCM Encryption

Data is encrypted using AES-256-GCM (Galois/Counter Mode), which provides both confidentiality and authenticity verification.

Output Format

The output is formatted as v1:salt:iv:ciphertext where all components are base64 encoded.

Encryption Functions

Derive Key

Derive AES Key from Password
Uses PBKDF2 with SHA-256 and 600,000 iterations to derive a 256-bit AES-GCM key from the password.
async function deriveAesGcmKey(password, saltBytes) {
    const te = new TextEncoder();
    const baseKey = await crypto.subtle.importKey(
        'raw',
        te.encode(password),
        { name: 'PBKDF2' },
        false,
        ['deriveKey']
    );

    return crypto.subtle.deriveKey(
        {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: saltBytes,
            iterations: 600000, // OWASP 2025 recommended minimum
        },
        baseKey,
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt', 'decrypt']
    );
}

Helpers

Helper Functions
Base64 encoding and decoding utilities for byte arrays.
function bytesToBase64(bytes) {
    let binary = '';
    const len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
}

function base64ToBytes(base64) {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    return bytes;
}

Encrypt

Encrypt Data
Encrypts plaintext using AES-256-GCM with a random salt and IV. Returns formatted payload.
/**
 * Build combined key from password and salt key
 * Format: "password:saltKey"
 */
function buildCombinedKey(password, saltKey) {
    if (saltKey) {
        return password + ':' + saltKey;
    }
    return password;
}

async function encrypt(plainText, password, saltKey = '') {
    const te = new TextEncoder();
    const salt = crypto.getRandomValues(new Uint8Array(16));
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const combinedKey = buildCombinedKey(password, saltKey);
    const key = await deriveAesGcmKey(combinedKey, salt);

    const cipherBuf = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv },
        key,
        te.encode(plainText)
    );

    return [
        'v1',
        bytesToBase64(salt),
        bytesToBase64(iv),
        bytesToBase64(new Uint8Array(cipherBuf)),
    ].join(':');
}

Decrypt

Decrypt Data
Decrypts a v1 or v2 payload back to plaintext using the original password.
/**
 * Build combined key from password and salt key
 * Format: "password:saltKey"
 */
function buildCombinedKey(password, saltKey) {
    if (saltKey) {
        return password + ':' + saltKey;
    }
    return password;
}

async function decrypt(payload, password, saltKey = '') {
    const td = new TextDecoder();
    const [version, saltB64, ivB64, cipherB64] = payload.split(':');

    if (version !== 'v1') {
        throw new Error('Unsupported payload version');
    }

    const salt = base64ToBytes(saltB64);
    const iv = base64ToBytes(ivB64);
    const ciphertext = base64ToBytes(cipherB64);
    const combinedKey = buildCombinedKey(password, saltKey);
    const key = await deriveAesGcmKey(combinedKey, salt);

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

    return td.decode(plainBuf);
}

Complete Example

Ready-to-Use Code
Copy and paste this complete example into your browser console or JavaScript file.
/**
 * AES-256-GCM Encryption/Decryption Example
 * Encrypts "Hello World" and decrypts it back
 */

// Salt key (RK) - used to strengthen password-based encryption
const SALT_KEY = '9x=1KO2tUFw#G:ARZd>Ff)s(^H+DWY4MpgJ:Cp_pCUU|og$>6a.bS.;ij9Wnw';

// Helper functions
function bytesToBase64(bytes) {
    let binary = '';
    const len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
}

function base64ToBytes(base64) {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    return bytes;
}

/**
 * Build combined key from password and salt key
 * Format: "password:saltKey"
 */
function buildCombinedKey(password, saltKey) {
    if (saltKey) {
        return password + ':' + saltKey;
    }
    return password;
}

/**
 * Version configuration for encryption payloads
 * v1: 150,000 iterations (legacy)
 * v2: 600,000 iterations (current, OWASP 2025 recommended)
 */
const VERSION_CONFIG = {
    v1: { iterations: 150000 },
    v2: { iterations: 600000 },
};
const CURRENT_VERSION = 'v2';

// Derive AES-256 key from password using PBKDF2
async function deriveAesGcmKey(password, saltBytes, iterations) {
    const te = new TextEncoder();
    const baseKey = await crypto.subtle.importKey(
        'raw',
        te.encode(password),
        { name: 'PBKDF2' },
        false,
        ['deriveKey']
    );

    return crypto.subtle.deriveKey(
        {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: saltBytes,
            iterations: iterations,
        },
        baseKey,
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt', 'decrypt']
    );
}

// Encrypt function (uses v2 with 600,000 iterations)
async function encrypt(plainText, password, saltKey = '') {
    const te = new TextEncoder();
    const salt = crypto.getRandomValues(new Uint8Array(16));
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const combinedKey = buildCombinedKey(password, saltKey);
    const iterations = VERSION_CONFIG[CURRENT_VERSION].iterations;
    const key = await deriveAesGcmKey(combinedKey, salt, iterations);

    const cipherBuf = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv },
        key,
        te.encode(plainText)
    );

    return [
        CURRENT_VERSION,
        bytesToBase64(salt),
        bytesToBase64(iv),
        bytesToBase64(new Uint8Array(cipherBuf)),
    ].join(':');
}

// Decrypt function (supports both v1 and v2)
async function decrypt(payload, password, saltKey = '') {
    const td = new TextDecoder();
    const [version, saltB64, ivB64, cipherB64] = payload.split(':');

    const versionConfig = VERSION_CONFIG[version];
    if (!versionConfig) {
        throw new Error('Unsupported payload version: ' + version);
    }

    const salt = base64ToBytes(saltB64);
    const iv = base64ToBytes(ivB64);
    const ciphertext = base64ToBytes(cipherB64);
    const combinedKey = buildCombinedKey(password, saltKey);
    const key = await deriveAesGcmKey(combinedKey, salt, versionConfig.iterations);

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

    return td.decode(plainBuf);
}

// Example usage
(async () => {
    const message = 'Hello World';
    const password = 'my-secret-password';

    console.log('Original:', message);

    // Encrypt with salt key (uses v2)
    const encrypted = await encrypt(message, password, SALT_KEY);
    console.log('Encrypted:', encrypted);

    // Decrypt with same salt key (supports v1 and v2)
    const decrypted = await decrypt(encrypted, password, SALT_KEY);
    console.log('Decrypted:', decrypted);
})();

Expected Output

Expected Console Output
Original: Hello World
Encrypted: v1:aBcDeFgHiJkLmNoP...:qRsTuVwXyZ...:encrypted_data...
Decrypted: Hello World

Note: The encrypted output will be different each time due to random salt and IV generation, but decryption will always return the original message.

Ready to Try It?

Use our interactive encryption tool to see this encryption in action.

Try Encryption Tool

We barely use cookies

We only use strictly necessary cookies, that enhance website security and improve user experience. We don't track your actions or activity.