Skip to content

Instantly share code, notes, and snippets.

@piroor
Last active August 16, 2022 08:02
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piroor/a312990473fbbef94ce63309278874f0 to your computer and use it in GitHub Desktop.
Save piroor/a312990473fbbef94ce63309278874f0 to your computer and use it in GitHub Desktop.
Example of AES-GCM encryptor with passphrase, based on Web Crypto API
async function getKey(passphrase, salt = null) {
passphrase = (new TextEncoder()).encode(passphrase);
let digest = await crypto.subtle.digest({ name: 'SHA-256' }, passphrase);
let keyMaterial = await crypto.subtle.importKey(
'raw',
digest,
{ name: 'PBKDF2' },
false,
['deriveKey']
);
if (!salt)
salt = crypto.getRandomValues(new Uint8Array(16));
let key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
return [key, salt];
}
function getFixedField() {
let value = localStorage.getItem('96bitIVFixedField');
if (value)
return Uint8Array.from(JSON.parse(value));
value = crypto.getRandomValues(new Uint8Array(12));
localStorage.setItem('96bitIVFixedField', JSON.stringify(Array.from(value)));
return value;
}
function getInvocationField() {
let counter = localStorage.getItem('32bitLastCounter');
if (counter)
counter = Uint32Array.from(JSON.parse(counter));
else
counter = new Uint32Array(1);
counter[0]++;
localStorage.setItem('32bitLastCounter', JSON.stringify(Array.from(counter)));
return counter;
}
async function encrypt(input, passphrase) {
let [key, salt] = await getKey(passphrase);
let fixedPart = getFixedField();
let invocationPart = getInvocationField();
let iv = Uint8Array.from([...fixedPart, ...new Uint8Array(invocationPart.buffer)]);
let encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
(new TextEncoder()).encode(JSON.stringify(input))
);
encryptedData = Array.from(new Uint8Array(encryptedData), char => String.fromCharCode(char)).join('');
return JSON.stringify([
btoa(encryptedData),
Array.from(invocationPart),
Array.from(salt)
]);
}
async function decrypt(encryptedResult, passphrase) {
let [encryptedData, invocationPart, salt] = JSON.parse(encryptedResult);
let [key, _] = await getKey(passphrase, Uint8Array.from(salt));
let invocationPartTypedArray = new Uint32Array(1);
invocationPartTypedArray[0] = invocationPart;
let iv = Uint8Array.from([...getFixedField(), ...(new Uint8Array(invocationPartTypedArray.buffer))]);
encryptedData = atob(encryptedData);
encryptedData = Uint8Array.from(encryptedData.split(''), char => char.charCodeAt(0));
let decryptedData = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encryptedData
);
decryptedData = (new TextDecoder()).decode(new Uint8Array(decryptedData));
return JSON.parse(decryptedData);
}
@piroor
Copy link
Author

piroor commented Feb 8, 2019

Usage:

(async () => {
  let passphrase = 'Open Sesami';


  // Encryption/decryption of a string

  let encrypted = await encrypt('The king has donkey ears!', passphrase);
  console.log(encrypted);
  // => '["KoYoXAWjY1lAheEZrHYwAkbOf4e/kr8wgbVEPwNEjTawg3HTLmvvuXOqNn+R",[1,0,0,0,0,0,0,0]]'

  let decrypted = await decrypt(encrypted, passphrase);
  console.log(decrypted);
  // => 'The king has donkey ears!'


  // Encryption/decryption of an object

  encrypted = await encrypt({ a: 0, b: 1, c: true, d: 'foobar' }, passphrase);
  console.log(encrypted);
  // => '["bkvTkQNfTfnP7uUirivktij4iy66pSbiBDYJ3uNChkIlDJPBdkJ4Tqbe98a+QSujoHME",[2,0,0,0,0,0,0,0]]'

  decrypted = await decrypt(encrypted, passphrase);
  console.log(decrypted);
  // => Object { a: 0, b: 1, c: true, d: "foobar" }
})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment