Skip to content

Instantly share code, notes, and snippets.

@lukethacoder
Last active March 14, 2020 12:06
Show Gist options
  • Save lukethacoder/3fac674637cb63ac6d97275e16eb0861 to your computer and use it in GitHub Desktop.
Save lukethacoder/3fac674637cb63ac6d97275e16eb0861 to your computer and use it in GitHub Desktop.
window.crypto - AES-GCM encryption feat. sessionStorage
// Encryption/decryption of an object
let encrypted = await encrypt(
{ a: 0, b: 1, c: true, d: 'foobar' },
SUPER_SECRET_PASSPHRASE
);
console.log(encrypted);
// => '["bkvTkQNfTfnP7uUirivktij4iy66pSbiBDYJ3uNChkIlDJPBdkJ4Tqbe98a+QSujoHME",[2,0,0,0,0,0,0,0]]'
let decrypted = await decrypt(encrypted, SUPER_SECRET_PASSPHRASE);
console.log(decrypted);
// => Object { a: 0, b: 1, c: true, d: "foobar" }
const SESSION_KEY = 'contact_id';
const id_raw = sessionStorage.getItem(SESSION_KEY);
if (contact_id_raw) {
let decrypted = await decrypt(id_raw, SUPER_SECRET_PASSPHRASE);
console.log(decrypted);
// => 'C0NT4CT-1D-G03S-H343'
} else {
// Encryption/decryption of a string
let encrypted = await encrypt(
'C0NT4CT-1D-G03S-H343',
SUPER_SECRET_PASSPHRASE
);
console.log(encrypted);
sessionStorage.setItem(SESSION_KEY, encrypted);
// => '["KoYoXAWjY1lAheEZrHYwAkbOf4e/kr8wgbVEPwNEjTawg3HTLmvvuXOqNn+R",[1,0,0,0,0,0,0,0]]'
}
export 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];
}
export 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;
}
export 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;
}
export 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)
]);
}
export 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment