Created
July 31, 2025 19:54
-
-
Save sadiqsalau/434df3e9b1f1095fa85168439dbd8570 to your computer and use it in GitHub Desktop.
PWA Offline Encryption
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
| import { gcm as aes256gcm, randomBytes } from "@noble/ciphers/webcrypto"; | |
| import { base64 } from "@scure/base"; | |
| import { scryptAsync } from "@noble/hashes/scrypt"; | |
| const VERSION = 1; | |
| const SALT_BYTES = 32; | |
| const NONCE_BYTES = 12; | |
| const KEY_BYTES = 32; | |
| function generateSalt() { | |
| return base64.encode(randomBytes(SALT_BYTES)); | |
| } | |
| async function scryptPass(password, saltB64) { | |
| const salt = base64.decode(saltB64); | |
| return await scryptAsync(password, salt, { | |
| N: 2 ** 15, | |
| r: 8, | |
| p: 1, | |
| dkLen: KEY_BYTES, | |
| }); | |
| } | |
| export async function encrypt({ text, password, salt, nonce }) { | |
| const saltB64 = salt || generateSalt(); | |
| const iv = nonce || randomBytes(NONCE_BYTES); | |
| const key = await scryptPass(password, saltB64); | |
| const cipher = aes256gcm(key, iv); | |
| const encrypted = await cipher.encrypt(new TextEncoder().encode(text)); | |
| const bundle = new Uint8Array(1 + iv.length + encrypted.length); | |
| bundle.set([VERSION]); | |
| bundle.set(iv, 1); | |
| bundle.set(encrypted, 1 + iv.length); | |
| return { | |
| encrypted: base64.encode(bundle), | |
| salt: saltB64, | |
| }; | |
| } | |
| export async function decrypt({ encrypted, password, salt }) { | |
| const bundle = base64.decode(encrypted); | |
| const version = bundle[0]; | |
| if (version !== VERSION) throw new Error("Unsupported cipher version"); | |
| const iv = bundle.slice(1, 1 + NONCE_BYTES); | |
| const cipherText = bundle.slice(1 + NONCE_BYTES); | |
| const key = await scryptPass(password, salt); | |
| const cipher = aes256gcm(key, iv); | |
| const decrypted = await cipher.decrypt(cipherText); | |
| if (!decrypted) throw new Error("Invalid password or corrupted data"); | |
| return new TextDecoder().decode(decrypted); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment