149 lines
4.4 KiB
JavaScript
149 lines
4.4 KiB
JavaScript
'use strict';
|
|
|
|
/* Utilities for scrambling strings to obscure puzzle answers. Not
|
|
* intended to be secure. */
|
|
|
|
// From https://stackoverflow.com/a/34310051
|
|
function toHexString(buffer) {
|
|
let byteArray = new Uint8Array(buffer);
|
|
return Array.from(byteArray, function(byte) {
|
|
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
|
}).join('');
|
|
}
|
|
function fromHexString(str) {
|
|
let buffer = new ArrayBuffer(str.length / 2);
|
|
let byteArray = new Uint8Array(buffer);
|
|
let i = 0;
|
|
for (let idx = 0; idx < str.length; idx += 2) {
|
|
let hex = str.substring(idx, idx+2);
|
|
byteArray[i++] = parseInt(hex, 16);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
// "Encrypt" a string and include the key just as a way of scrambling it
|
|
// to make it hard to read for a human.
|
|
async function getDefaultKeyAndIv() {
|
|
let key = await window.crypto.subtle.importKey('raw', new ArrayBuffer(32),
|
|
{ name: "AES-GCM" }, false, ['encrypt', 'decrypt']);
|
|
let iv = new Uint8Array(12);
|
|
return [key, iv];
|
|
}
|
|
async function encryptStringNoKey(plaintext) {
|
|
let encoded = new TextEncoder().encode(plaintext);
|
|
let [key, iv] = await getDefaultKeyAndIv();
|
|
let ciphertext = await window.crypto.subtle.encrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: iv
|
|
},
|
|
key,
|
|
encoded
|
|
);
|
|
return toHexString(ciphertext);
|
|
}
|
|
async function decryptStringNoKey(ciphertextStr) {
|
|
let [key, iv] = await getDefaultKeyAndIv();
|
|
let ciphertext = fromHexString(ciphertextStr);
|
|
|
|
let decrypted = await window.crypto.subtle.decrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: iv
|
|
},
|
|
key,
|
|
ciphertext
|
|
);
|
|
let plaintext = new TextDecoder().decode(decrypted);
|
|
return plaintext;
|
|
}
|
|
async function encryptString(plaintext) {
|
|
let encoded = new TextEncoder().encode(plaintext);
|
|
let key = await window.crypto.subtle.generateKey(
|
|
{
|
|
name: "AES-GCM",
|
|
length: 256,
|
|
},
|
|
true,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
let iv = await window.crypto.getRandomValues(new Uint8Array(12));
|
|
let ciphertext = await window.crypto.subtle.encrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: iv
|
|
},
|
|
key,
|
|
encoded
|
|
);
|
|
let keyBuffer = await window.crypto.subtle.exportKey('raw', key);
|
|
return toHexString(keyBuffer) + ','
|
|
+ toHexString(iv) + ','
|
|
+ toHexString(ciphertext);
|
|
}
|
|
async function decryptString(str) {
|
|
let [keyStr, ivStr, ciphertextStr] = str.split(',');
|
|
let keyBuffer = fromHexString(keyStr);
|
|
let iv = fromHexString(ivStr);
|
|
let ciphertext = fromHexString(ciphertextStr);
|
|
|
|
let key = await window.crypto.subtle.importKey('raw', keyBuffer,
|
|
{ name: "AES-GCM" }, false, ['decrypt']);
|
|
let decrypted = await window.crypto.subtle.decrypt(
|
|
{
|
|
name: "AES-GCM",
|
|
iv: iv
|
|
},
|
|
key,
|
|
ciphertext
|
|
);
|
|
let plaintext = new TextDecoder().decode(decrypted);
|
|
return plaintext;
|
|
}
|
|
|
|
async function _scrambleString(str, kind) {
|
|
if (kind[0] == 'B') {
|
|
return 'B' + btoa(await _scrambleString(str, kind.substring(1)));
|
|
} else if (kind[0] == 'E') {
|
|
return 'E' + await encryptString(
|
|
await _scrambleString(str, kind.substring(1)));
|
|
} else if (kind[0] == 'A') {
|
|
return 'A' + await encryptStringNoKey(
|
|
await _scrambleString(str, kind.substring(1)));
|
|
} else if(!kind) {
|
|
return str;
|
|
} else {
|
|
throw new Exception("Unknown scramble kind: " + kind);
|
|
}
|
|
}
|
|
function scrambleString(str, scrambleKinds) {
|
|
let res = null;
|
|
for (let i in scrambleKinds) {
|
|
let kind = scrambleKinds[i];
|
|
if (res === null) {
|
|
res = _scrambleString(str, kind);
|
|
} else {
|
|
res = res.catch(_ => _scrambleString(str, kind));
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
async function unscrambleString(str) {
|
|
try {
|
|
if (str[0] == 'B') {
|
|
return await unscrambleString(atob(str.substring(1)));
|
|
} else if (str[0] == 'E') {
|
|
return await unscrambleString(
|
|
await decryptString(str.substring(1)));
|
|
} else if (str[0] == 'A') {
|
|
return await unscrambleString(
|
|
await decryptStringNoKey(str.substring(1)));
|
|
} else {
|
|
return str;
|
|
}
|
|
}
|
|
catch(e) {
|
|
return null;
|
|
}
|
|
}
|