const ITERATIONS = 100000; const SALT_LENGTH = 16; const IV_LENGTH = 12; const enc = new TextEncoder(); const dec = new TextDecoder(); export const encrypt = async (data: string, password: string) => { const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH)); const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); const passwordKey = await getPasswordKey(password); const aesKey = await deriveKey(passwordKey, salt, ['encrypt']); const encryptedContent = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, }, aesKey, enc.encode(data) ); const encryptedContentArr = new Uint8Array(encryptedContent); const buf = new Uint8Array( salt.byteLength + iv.byteLength + encryptedContentArr.byteLength ); buf.set(salt, 0); buf.set(iv, salt.byteLength); buf.set(encryptedContentArr, salt.byteLength + iv.byteLength); return bufToBase64(buf); }; export const decrypt = async (encryptedData: string, password: string) => { const encryptedDataBuff = base64ToBuf(encryptedData); const salt = encryptedDataBuff.slice(0, SALT_LENGTH); const iv = encryptedDataBuff.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); const data = encryptedDataBuff.slice(SALT_LENGTH + IV_LENGTH); const passwordKey = await getPasswordKey(password); const aesKey = await deriveKey(passwordKey, salt, ['decrypt']); const decryptedContent = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, }, aesKey, data ); return dec.decode(decryptedContent); }; const getPasswordKey = (password: string) => { return crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, [ 'deriveKey', ]); }; const deriveKey = ( passwordKey: CryptoKey, salt: Uint8Array, keyUsages: KeyUsage[] ) => { return crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: ITERATIONS, hash: 'SHA-256', }, passwordKey, { name: 'AES-GCM', length: 256 }, false, keyUsages ); }; const bufToBase64 = (buf: Uint8Array) => { let s = ''; buf.forEach((c) => { s += String.fromCharCode(c); }); return btoa(s); }; const base64ToBuf = (b64: string) => { return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); };