Last active
February 23, 2026 18:17
-
-
Save lord-max/e42e7c9709993272b4324d4ba355c6c8 to your computer and use it in GitHub Desktop.
Simple encrypted text notes using default browser crypto API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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