Public
Edited
Oct 14, 2022
Importers
Insert cell
Insert cell
Insert cell
{ // Demonstrate what a "byte string" is in Javascript
const toByteStr = (a) => String.fromCharCode(...a);
const fromByteStr = (s) => s.split("").map(c => c.charCodeAt(0));

let foo = [255, 255, 255];
return [
toByteStr(foo),
fromByteStr(toByteStr(foo)),
]
}
Insert cell
// showing the 0 filling on parseInt after character 14. 4 zero's are added when we use a 20 char string
[c1_1.input.slice(0,20), parseInt(c1_1.input.slice(0,20), 16).toString(16)]
Insert cell
Insert cell
lib.fromHex(c1_1.input)
Insert cell
lib.tob64(lib.fromHex(c1_1.input))
Insert cell
lib.tob64(lib.fromHex(c1_1.input)) == c1_1.output
Insert cell
Insert cell
Insert cell
o1_2 = {
const a1 = lib.fromHex(c1_2.input1);
const a2 = lib.fromHex(c1_2.input2);

return lib.toHex(a1.map((c, i) => c ^ a2[i]));
}
Insert cell
o1_2 === c1_2.output
Insert cell
Insert cell
Insert cell
singleXor = (arr, k) => arr.map(c => lib.itoa(c^k)).join("")
Insert cell
Insert cell
Insert cell
// By inspection
brute_force[88]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
suspectKey1 = most_common_input ^ lib.atoi(most_common_mb)
Insert cell
c1_3_result = singleXor(lib.fromHex(c1_3.input), suspectKey1)
Insert cell
sortedFreq = Object.keys(mobydick_freqs).map(k => {return {'freq': mobydick_freqs[k], char: (k === " ") ? "<space>" : k}}).sort((a, b)=> b.freq - a.freq)
Insert cell
sortFreqs = (fs) => Object.keys(fs).map(k => {return {'freq': fs[k], char: (k === " ") ? "<space>" : k}}).sort((a, b)=> b.freq - a.freq)
Insert cell
sortedFreq[96]
Insert cell
truncatedFreqs = sortedFreq.slice(0, 50)
Insert cell
Insert cell
inFreq = Object.keys(c1_3_infreq).map(k => {return {'freq': c1_3_infreq[k], char: (k === " ") ? "<space>" : k}}).sort((a, b)=> b.freq - a.freq)
Insert cell
Insert cell
Insert cell
Insert cell
## Chi Square test

* https://en.wikipedia.org/wiki/Chi-squared_test
* http://boris.ryabko.net/jspi.pdf


Insert cell
makeRelative = (f) => {
const total = Object.values(f).reduce((v, acc) => acc + v, 0);

const rel = {};
Object.keys(f).map((k) => rel[k] = f[k]/total);

return rel;
}
Insert cell
mbd_rel = makeRelative(mobydick_freqs);
Insert cell
chiSqScore = (pf, bf) => {

const scores = Object.keys(pf).map(k => {
if (k === "'") {
return Math.pow(pf[k] - bf['‘'], 2)/bf['‘'];
} else if (bf[k] === undefined) {
// This means the character doesn't ever occur in the base set
// (e.g. Moby Dick never has this character). If you're being strict,
// you should immediately reject this, but since we're being relative
// we use the least common character in moby dick to represent the frequency of that character.
//console.log(`bad k ${k}`);

return Math.pow(pf[k] - bf['£'], 2)/bf['£'];

return NaN;
}
return Math.pow(pf[k] - bf[k], 2)/bf[k];
});

//return scores;
return scores.reduce((c, a) => a + c, 0)
}
Insert cell
{
const s = brute_force[88];
const f1 = makeFreqs(s.split(""));

return {key: 88, s: s, score: chiSqScore(
makeRelative(f1),
mbd_rel
)}
}
Insert cell
makeRelative(makeFreqs(brute_force[5].split("")))
Insert cell
{

const mbd_rel = makeRelative(mobydick_freqs);
const rs = brute_force.map((s, i) => {
const f1 = makeFreqs(s.split(""));

return {key: i, s: s, score: chiSqScore(
makeRelative(f1),
mbd_rel
)}
});

return rs.filter(x => x.score >0).sort((a, b) => a.score - b.score)

}
Insert cell
Insert cell
c1_4 = {
const texts = (await FileAttachment("4.txt").text()).split("\n");
const bytes = texts.map(lib.fromHex);

return {texts, bytes};
}
Insert cell
getSpaceChiSquare = (arr, i) => {
// Get frequency of bytes in cipher text
const rawFreqs = makeFreqs(arr);
const relFreqs = makeRelative(rawFreqs);
const freqs = sortFreqs(relFreqs);
const mostCommon = freqs[0];

// Assume that character is " ", so xor it with space to get our
// top key candidate
const key = lib.atoi(" ") ^ parseInt(mostCommon.char);
const r = singleXor(arr, key);


// Now get the frequencies of our decrypted text
const cFreqs = makeRelative(makeFreqs(r.split("")));

// Get the Chi Square to indicate to let us sort on how
// likely this is English text (low Chi square -> more likely its English)
const score = chiSqScore(cFreqs, mbd_rel)

return {i, hex: lib.toHex(arr), score, r, key};
};
Insert cell
c1_4.bytes.map(getSpaceChiSquare).sort((a, b) => a.score - b.score)[0]
Insert cell
findBestChiKey = (arr) => {
// Get frequency of bytes in cipher text
const rawFreqs = makeFreqs(arr);
const relFreqs = makeRelative(rawFreqs);
const freqs = sortFreqs(relFreqs);
// const mostCommon = freqs[0];

const sols = [];

for (let k = 0; k < 255; k += 1) {
const r = singleXor(arr, k);
// Now get the frequencies of our decrypted text
const cFreqs = makeRelative(makeFreqs(r.split("")));
// Get the Chi Square to indicate to let us sort on how
// likely this is English text (low Chi square -> more likely its English)
const score = chiSqScore(cFreqs, mbd_rel);

sols.push({k, hex: lib.toHex(arr), score, r});
}

return sols.sort((a, b) => a.score - b.score);
}
Insert cell
findBestChiKey(c1_4.bytes[170])
Insert cell
Insert cell
lib.fromHex("ffeeaagg")
Insert cell
parseInt("ff", 16)
Insert cell
lib = {
const hexAlphabet = "0123456789abcdef";

const toByteStr = (a) => String.fromCharCode(...a);
const fromByteStr = (s) => s.split("").map(c => c.charCodeAt(0));

const toHex1 = (x) => {
let hex = x.toString(16);
if (hex.length == 1) { hex = "0" + hex;}
return hex
};


return {
toByteStr, fromByteStr,
atoi: (a) => a.charCodeAt(0),
itoa: (i) => String.fromCharCode(i),

toHex1,
toHex: (s) => s.map(toHex1).join(""),

fromHex: (s) => {

if ((s.length % 2) !== 0) {
throw 'hex string must have length divisible by 2';
}

let ints = [];

for (let i = 0; i < s.length; i += 2) {
let c1 = s[i];
let c2 = s[i+1];

let b = hexAlphabet.indexOf(c2) + (hexAlphabet.indexOf(c1) << 4);

ints.push(b);
}

return ints;

},

tob64: (arr) => btoa(toByteStr(arr)),
fromb64: (s) => fromByteStr(atob(s)),


fromBinArr: (x) => {
let result = 0;

for (let i = 0; i < 8; i += 1) {
result |= x[7 - i] << i
}

return result;
},

toBinArr: (c) => {
if (c > 255) {
throw 'must be 8 bit byte';
}

let result = [];

result[7] = (c & 1) > 0 ? 1 : 0;
result[6] = (c & 2) > 0 ? 1 : 0;
result[5] = (c & 4) > 0 ? 1 : 0;
result[4] = (c & 8) > 0 ? 1 : 0;
result[3] = (c & 16) > 0 ? 1 : 0;
result[2] = (c & 32) > 0 ? 1 : 0;
result[1] = (c & 64) > 0 ? 1 : 0;
result[0] = (c & 128) > 0 ? 1 : 0;

return result;
}
}
}
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more