Last active
March 14, 2020 12:06
-
-
Save lukethacoder/3fac674637cb63ac6d97275e16eb0861 to your computer and use it in GitHub Desktop.
window.crypto - AES-GCM encryption feat. sessionStorage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]]' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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