Skip to content

Instantly share code, notes, and snippets.

@lord-max
Last active February 23, 2026 18:17
Show Gist options
  • Select an option

  • Save lord-max/e42e7c9709993272b4324d4ba355c6c8 to your computer and use it in GitHub Desktop.

Select an option

Save lord-max/e42e7c9709993272b4324d4ba355c6c8 to your computer and use it in GitHub Desktop.
Simple encrypted text notes using default browser crypto API
/**
* Simple encrypted notes via default browser crypto.subtle
*
* This implementation uses:
* - PBKDF2 for password-based key derivation
* - AES-GCM (256-bit) for authenticated encryption
*/
/**
* Derives an AES-GCM 256-bit key from a password using PBKDF2.
* @param {string} password
* @param {Uint8Array} salt - 16-byte salt
* @param {number} [iterations=1e6]
* @returns {Promise<CryptoKey>}
*/
const rng = (s) => { return crypto.getRandomValues(new Uint8Array(s)) }
async function deriveKey(password, salt, iterations = 1e6) {
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, hash: "SHA-256" },
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
/**
* Encrypts a plaintext string with a password.
* Returns a base64-encoded string containing: salt (16B) + iv (12B) + ciphertext.
*
* @param {string} plaintext - The text to encrypt
* @param {string} password - The password to derive the key from
* @returns {Promise<string>} Base64-encoded encrypted payload
*/
async function encrypt(plaintext, password) {
const enc = new TextEncoder();
const salt = rng(16);
const iv = rng(12);
const key = await deriveKey(password, salt);
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(plaintext)
);
// Concatenate salt + iv + ciphertext into a single buffer
const result = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
result.set(salt, 0);
result.set(iv, salt.length);
result.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(...result));
}
/**
* Decrypts a base64-encoded payload produced by `encrypt()`.
*
* @param {string} encryptedBase64 - The base64-encoded encrypted payload
* @param {string} password - The password used during encryption
* @returns {Promise<string>} The decrypted plaintext
*/
async function decrypt(encryptedBase64, password) {
const dec = new TextDecoder();
const data = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
const salt = data.slice(0, 16);
const iv = data.slice(16, 28);
const ciphertext = data.slice(28);
const key = await deriveKey(password, salt);
const plainBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertext
);
return dec.decode(plainBuffer);
}
// ── Usage Example ──────────────────────────────────────────────
async function cnote(
message = prompt("Enter your message"),
ctext = prompt("Enter your ciphertext"),
password = prompt("Enter your password"))
{
let encrypted = "";
if (message.length > 1) {
encrypted = await encrypt(message, password);
console.log("Encrypted:", encrypted);
alert(encrypted);
//await navigator.clipboard.writeText(encrypted);
}
if (ctext.length > 10) encrypted = ctext;
const decrypted = await decrypt(encrypted, password);
alert(decrypted);
}
cnote("", "NCPuXl4zPYa1p7pWmRuh7+FnKbWBicLpMFggG02Ze9GPPQxHPRszZK4eDObjxE/IGw==", "world")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment