Skip to content

Instantly share code, notes, and snippets.

@scyilk
Forked from chrisveness/crypto-aes-gcm.js
Created February 11, 2024 19:54
Show Gist options
  • Save scyilk/9b07ad22ec2bc7811a939c40832ae984 to your computer and use it in GitHub Desktop.
Save scyilk/9b07ad22ec2bc7811a939c40832ae984 to your computer and use it in GitHub Desktop.

Revisions

  1. @chrisveness chrisveness revised this gist Oct 3, 2021. 1 changed file with 18 additions and 16 deletions.
    34 changes: 18 additions & 16 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -15,21 +15,19 @@ async function aesGcmEncrypt(plaintext, password) {
    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 ivStr = Array.from(iv).map(b => String.fromCharCode(b)).join(''); // iv as utf-8 string

    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
    return btoa(ivStr+ctStr); // iv+ciphertext base64-encoded
    }


    @@ -46,21 +44,25 @@ async function aesGcmEncrypt(plaintext, password) {
    * 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 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 ivStr = atob(ciphertext).slice(0,12); // decode base64 iv
    const iv = new Uint8Array(Array.from(ivStr).map(ch => ch.charCodeAt(0))); // iv as Uint8Array

    const alg = { name: 'AES-GCM', iv: new Uint8Array(iv) }; // specify algorithm to use
    const alg = { name: 'AES-GCM', iv: iv }; // specify algorithm to use

    const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key
    const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // generate key from pw

    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
    const ctStr = atob(ciphertext).slice(12); // decode base64 ciphertext
    const ctUint8 = new Uint8Array(Array.from(ctStr).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
    try {
    const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key
    const plaintext = new TextDecoder().decode(plainBuffer); // plaintext from ArrayBuffer
    return plaintext; // return the plaintext
    } catch (e) {
    throw new Error('Decrypt failed');
    }
    }
  2. @chrisveness chrisveness revised this gist Oct 9, 2018. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -46,21 +46,21 @@ async function aesGcmEncrypt(plaintext, password) {
    * 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 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 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 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 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
    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
    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
    return plaintext; // return the plaintext
    }
  3. @chrisveness chrisveness revised this gist Mar 10, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,7 @@
    *
    * @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
    @@ -42,6 +43,7 @@ async function aesGcmEncrypt(plaintext, password) {
    *
    * @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
  4. @chrisveness chrisveness revised this gist Mar 10, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    /**
    * 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.
    @@ -33,6 +34,7 @@ async function aesGcmEncrypt(plaintext, password) {

    /**
    * 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.
  5. @chrisveness chrisveness revised this gist Mar 10, 2018. 1 changed file with 10 additions and 28 deletions.
    38 changes: 10 additions & 28 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -42,39 +42,21 @@ async function aesGcmEncrypt(plaintext, password) {
    * 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(/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 pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8
    const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password

    const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key
    const iv = ciphertext.slice(0,24).match(/.{2}/g).map(byte => parseInt(byte, 16)); // get iv from ciphertext

    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 alg = { name: 'AES-GCM', iv: new Uint8Array(iv) }; // specify algorithm to use

    const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key
    const plaintext = new TextDecoder().decode(plainBuffer); // decode password from UTF-8
    const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key

    return plaintext; // return the plaintext
    }/g).map(ch => ch.charCodeAt(0))); // ciphertext as Uint8Array
    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
    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
    return plaintext; // return the plaintext
    }
  6. @chrisveness chrisveness revised this gist Mar 10, 2018. 1 changed file with 28 additions and 10 deletions.
    38 changes: 28 additions & 10 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -42,21 +42,39 @@ async function aesGcmEncrypt(plaintext, password) {
    * 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 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 iv = ciphertext.slice(0,24).match(/.{2}/g).map(byte => parseInt(byte, 16)); // get iv from ciphertext
    const ctStr = atob(ciphertext.slice(24)); // decode base64 ciphertext
    const ctUint8 = new Uint8Array(ctStr.match(/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 alg = { name: 'AES-GCM', iv: new Uint8Array(iv) }; // specify algorithm to use
    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 key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key
    const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key
    const plaintext = new TextDecoder().decode(plainBuffer); // decode password from UTF-8

    const ctStr = atob(ciphertext.slice(24)); // decode base64 ciphertext
    const ctUint8 = new Uint8Array(ctStr.match(/./g).map(ch => ch.charCodeAt(0))); // ciphertext as Uint8Array
    return plaintext; // return the plaintext
    }/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
    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
    return plaintext; // return the plaintext
    }
  7. @chrisveness chrisveness revised this gist Feb 25, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@ 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(16)); // get random iv
    const iv = crypto.getRandomValues(new Uint8Array(12)); // get 96-bit random iv

    const alg = { name: 'AES-GCM', iv: iv }; // specify algorithm to use

    @@ -45,13 +45,13 @@ 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,32).match(/.{2}/g).map(byte => parseInt(byte, 16)); // get iv from ciphertext
    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(32)); // decode base64 ciphertext
    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?

  8. @chrisveness chrisveness revised this gist Feb 18, 2017. No changes.
  9. @chrisveness chrisveness created this gist Feb 17, 2017.
    62 changes: 62 additions & 0 deletions crypto-aes-gcm.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    /**
    * 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(16)); // get 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,32).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(32)); // 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
    }