/** * Encrypts plaintext using AES-GCM with supplied password, for decryption with aesGcmDecrypt(). * (c) Chris Veness MIT Licence * * @param {String} plaintext - Plaintext to be encrypted. * @param {String} password - Password to use to encrypt plaintext. * @returns {String} Encrypted ciphertext. * * @example * const ciphertext = await aesGcmEncrypt('my secret text', 'pw'); * aesGcmEncrypt('my secret text', 'pw').then(function(ciphertext) { console.log(ciphertext); }); */ async function aesGcmEncrypt(plaintext, password) { const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8 const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password const iv = crypto.getRandomValues(new Uint8Array(12)); // get 96-bit random iv const alg = { name: 'AES-GCM', iv: iv }; // specify algorithm to use const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['encrypt']); // generate key from pw const ptUint8 = new TextEncoder().encode(plaintext); // encode plaintext as UTF-8 const ctBuffer = await crypto.subtle.encrypt(alg, key, ptUint8); // encrypt plaintext using key const ctArray = Array.from(new Uint8Array(ctBuffer)); // ciphertext as byte array const ctStr = ctArray.map(byte => String.fromCharCode(byte)).join(''); // ciphertext as string const ctBase64 = btoa(ctStr); // encode ciphertext as base64 const ivHex = Array.from(iv).map(b => ('00' + b.toString(16)).slice(-2)).join(''); // iv as hex string return ivHex+ctBase64; // return iv+ciphertext } /** * Decrypts ciphertext encrypted with aesGcmEncrypt() using supplied password. * (c) Chris Veness MIT Licence * * @param {String} ciphertext - Ciphertext to be decrypted. * @param {String} password - Password to use to decrypt ciphertext. * @returns {String} Decrypted plaintext. * * @example * const plaintext = await aesGcmDecrypt(ciphertext, 'pw'); * aesGcmDecrypt(ciphertext, 'pw').then(function(plaintext) { console.log(plaintext); }); */ async function aesGcmDecrypt(ciphertext, password) { const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8 const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password const iv = ciphertext.slice(0,24).match(/.{2}/g).map(byte => parseInt(byte, 16)); // get iv from ciphertext const alg = { name: 'AES-GCM', iv: new Uint8Array(iv) }; // specify algorithm to use const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key const ctStr = atob(ciphertext.slice(24)); // decode base64 ciphertext const ctUint8 = new Uint8Array(ctStr.match(/\s\S/g).map(ch => ch.charCodeAt(0))); // ciphertext as Uint8Array // note: why doesn't ctUint8 = new TextEncoder().encode(ctStr) work? const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key const plaintext = new TextDecoder().decode(plainBuffer); // decode password from UTF-8 return plaintext; // return the plaintext }