Skip to content

Instantly share code, notes, and snippets.

@pilcrowonpaper
Last active November 30, 2024 20:56
Show Gist options
  • Save pilcrowonpaper/353318556029221c8e25f451b91e5f76 to your computer and use it in GitHub Desktop.
Save pilcrowonpaper/353318556029221c8e25f451b91e5f76 to your computer and use it in GitHub Desktop.

Encrypting data with AES128 in JavaScript

Usage

AES128 uses a 16 byte secret key.

const key = new Uint8Array(16);
crypto.getRandomValues(key);

const message = "hello";

// Asynchronous when using the Web Crypto API.
const encrypted = await encryptString(key, message);
const decrypted = await decryptToString(key, encrypted);
console.log(new TextDecoder().decode(decrypted));

Use hex or base64 encoding if you need to store the encrypted data as a string (base64 is more compact). For cookies, use base64url.

import { encodeBase64urlNoPadding } from "@oslojs/encoding";

const encrypted = await encryptString(key, message);
const encoded = encodeBase64urlNoPadding(encrypted);

You can also openssl to generate the secret key in MacOS and Linux.

openssl rand --base64 16

On Windows, you can do this thing instead.

(1..16|%{[byte](Get-Random -Max 256)}|foreach ToString X2) -join ''

These will be base64-encoded so decode it before usage.

import { decodeBase64 } from "@oslojs/encoding";

const key = decodeBase64(process.env.ENCYRPTION_KEY);

Web Crypto API

// AES128 with the Web Crypto API.
async function encrypt(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
    const iv = new Uint8Array(16);
    crypto.getRandomValues(iv);
    const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
    const cipher = await crypto.subtle.encrypt(
        {
            name: "AES-GCM",
            iv,
            tagLength: 128
        },
        cryptoKey,
        data
    );
    const encrypted = new Uint8Array(iv.byteLength + cipher.byteLength);
    encrypted.set(iv);
    encrypted.set(new Uint8Array(cipher), iv.byteLength);
    return encrypted;
}
    
async function encryptString(key: Uint8Array, data: string): Uint8Array {
    const encoded = new TextEncoder().encode(data));
    const encrypted = await encrypt(encoded);
    return encrypted;
}

async function decrypt(key: Uint8Array, encrypted: Uint8Array): Promise<Uint8Array> {
    if (encrypted.length < 16) {
        throw new Error("Invalid data");
    }
    const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
    const decrypted = await crypto.subtle.decrypt(
        {
            name: "AES-GCM",
            iv: encrypted.slice(0, 16),
            tagLength: 128
        },
        cryptoKey,
        encrypted.slice(16)
    );
    return new Uint8Array(decrypted);
}
                              
async function decryptToString(key: Uint8Array, data: Uint8Array): string {
    const decrypted = await decrypt(key, data);
    const decoded = new TextDecoder().decode(decrypted);
    return decoded;
}

Node crypto module

import { createCipheriv, createDecipheriv } from "crypto";
import { DynamicBuffer } from "@oslojs/binary";

function encrypt(key: Uint8Array, data: Uint8Array): Uint8Array {
    const iv = new Uint8Array(16);
    crypto.getRandomValues(iv);
    const cipher = createCipheriv("aes-128-gcm", key, iv);
    const encrypted = new DynamicBuffer(0);
    encrypted.write(iv);
    encrypted.write(cipher.update(data));
    encrypted.write(cipher.final());
    encrypted.write(cipher.getAuthTag());
    return encrypted.bytes();
}

function encryptString(key: Uint8Array, data: string): Uint8Array {
    const encoded = new TextEncoder().encode(data));
    const encrypted = encrypt(encoded);
    return encrypted;
}

function decrypt(key: Uint8Array, encrypted: Uint8Array): Uint8Array {
    if (encrypted.byteLength < 33) {
        throw new Error("Invalid data");
    }
    const decipher = createDecipheriv("aes-128-gcm", key, encrypted.slice(0, 16));
    decipher.setAuthTag(encrypted.slice(encrypted.byteLength - 16));
    const decrypted = new DynamicBuffer(0);
    decrypted.write(decipher.update(encrypted.slice(16, encrypted.byteLength - 16)));
    decrypted.write(decipher.final());
    return decrypted.bytes();
}

function decryptToString(key: Uint8Array, data: Uint8Array): string {
    const decrypted = decrypt(key, data);
    const decoded = new TextDecoder().decode(decrypted);
    return decoded;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment