const ALGORITHM_NAME = "AES-GCM"; const ALGORITHM_LENGTH = 256; const IV_LENGTH = 16; const TEXT_ENCODER = new TextEncoder(); const TEXT_DECODER = new TextDecoder(); /** Coverts an ArrayBuffer into a Base64 encoded string * @param {ArrayBuffer} buffer * @returns {string} */ function bufferToBase64(buffer) { let binary = ""; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } /** Converts a Base64 encoded string into an ArrayBuffer * @param {string} base64 * @returns {ArrayBuffer} */ function base64ToBuffer(base64) { const binaryString = atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } /** Generates an key and encrypts "password" using it * @param {Promise} password */ async function encryptPassword(password) { const iv = new Uint8Array(IV_LENGTH); window.crypto.getRandomValues(iv); const key = await window.crypto.subtle.generateKey( { name: ALGORITHM_NAME, length: ALGORITHM_LENGTH, }, true, ["encrypt", "decrypt"], ); const encryptedPassword = await window.crypto.subtle.encrypt( { name: ALGORITHM_NAME, iv: iv, }, key, TEXT_ENCODER.encode(password), ); const exportedKey = await window.crypto.subtle.exportKey("raw", key); const encodedPassword = bufferToBase64(encryptedPassword); const encodedKey = bufferToBase64(exportedKey); const encodedIv = bufferToBase64(iv.buffer); return { password: encodedPassword, key: encodedKey, iv: encodedIv, }; } /** Decodes to key and iv and decryptes the password * @param {string} password * @param {string} key * @param {string} iv * @returns {Promise} */ async function decryptPassword(password, key, iv) { const decodedPassword = base64ToBuffer(password); const decodedKey = base64ToBuffer(key); const decodedIv = base64ToBuffer(iv); const cryptoKey = await window.crypto.subtle.importKey( "raw", decodedKey, { name: ALGORITHM_NAME, length: ALGORITHM_LENGTH, }, true, ["encrypt", "decrypt"], ); const decryptedPassword = await window.crypto.subtle.decrypt( { name: ALGORITHM_NAME, iv: decodedIv, }, cryptoKey, decodedPassword, ); return TEXT_DECODER.decode(decryptedPassword); }