Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Uses the SubtleCrypto interface of the Web Cryptography API to encrypt and decrypt text using AES-GCM (AES Galois counter mode).
/**
* Encrypts plaintext using AES-GCM with supplied password, for decryption with aesGcmDecrypt().
*
* @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');
*/
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.
*
* @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');
*/
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(/./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
}
@chrisveness
Owner

Cryptography is indeed subtle. If I have made any errors, let me know and I will attempt to correct.

Thx to Tim Taubert @ Mozilla.

@chrisveness
Owner

With the benefit of node-webcrypto-ossl and text-encoding polyfills, these routines will run on Node.js, and will inter-operate between the browser and the server.

Simply add the following at the top of the file:

const TextEncoder = require('text-encoding').TextEncoder;
const TextDecoder = require('text-encoding').TextDecoder;
const WebCrypto = require("node-webcrypto-ossl");
const crypto = new WebCrypto();
module.exports = { aesGcmEncrypt, aesGcmDecrypt };

Then to use it,

const aesGcmEncrypt = require ('./crypto-aes-gcm.js').aesGcmEncrypt;
const aesGcmDecrypt = require ('./crypto-aes-gcm.js').aesGcmDecrypt;

const ciphertext = await aesGcmEncrypt('my secret text', 'pw');
const plaintext = await aesGcmDecrypt(ciphertext, 'pw');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment