class PasswordCrypto { #encoder #decoder #algorithmName #algorithmLength #ivLength constructor() { this.#encoder = new TextEncoder() this.#decoder = new TextDecoder() this.#algorithmName = "AES-GCM" this.#algorithmLength = 256 this.#ivLength = 16 } /** * @param {string} buffer * @returns {string} */ #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) } /** * @param {string} base64 * @returns {string} */ #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 } /** * @param {string} password Plain text password * @returns {Promise<{ password: string, key: string, iv: string }>} Encrypted password */ async encryptPassword(password) { const iv = new Uint8Array(this.#ivLength) window.crypto.getRandomValues(iv) const key = await window.crypto.subtle.generateKey( { name: this.#algorithmName, length: this.#algorithmLength, }, true, ["encrypt", "decrypt"], ) const rawKey = await window.crypto.subtle.exportKey("raw", key) const encrypted = await window.crypto.subtle.encrypt( { name: this.#algorithmName, iv: iv, }, key, this.#encoder.encode(password), ) return { password: this.#bufferToBase64(encrypted), key: this.#bufferToBase64(rawKey), iv: this.#bufferToBase64(iv.buffer), } } /** * @param {string} password Encrypted password * @param {string} key * @param {string} iv * @returns {Promise} Plain text password */ async decryptPassword(password, key, iv) { const ckey = await window.crypto.subtle.importKey( "raw", this.#base64ToBuffer(key), { name: this.#algorithmName, length: this.#algorithmLength, }, true, ["encrypt", "decrypt"], ) const dpassword = await window.crypto.subtle.decrypt( { name: this.#algorithmName, iv: this.#base64ToBuffer(iv), }, ckey, this.#base64ToBuffer(password), ) return this.#decoder.decode(dpassword) } }