1
0
Fork 0
anagram-games/www/scramblestring.js

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;
}
}