Created
October 11, 2017 12:42
-
-
Save IGI-111/d1e26d549ab59526eb1fca34e55dd00b to your computer and use it in GitHub Desktop.
Vigenere cypher solver
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 fs = require('fs'); | |
const englishFrequency = [ | |
0.08167, | |
0.01492, | |
0.02782, | |
0.04253, | |
0.12702, | |
0.02228, | |
0.02015, | |
0.06094, | |
0.06966, | |
0.00153, | |
0.00772, | |
0.04025, | |
0.02406, | |
0.06749, | |
0.07507, | |
0.01929, | |
0.00095, | |
0.05987, | |
0.06327, | |
0.09056, | |
0.02758, | |
0.00978, | |
0.02360, | |
0.00150, | |
0.01974, | |
0.00074 | |
]; | |
function kasikiExamination (ciphertext) { // find probable keyword length | |
ciphertext = ciphertext.replace(/[^a-z0-9]/gi, '').toUpperCase(); | |
// find intervals repeated occurences of three or more characters | |
const regex = /(\S{3,})(.*)\1/mig; | |
let factors = {}; | |
let m; | |
while ((m = regex.exec(ciphertext)) !== null) { | |
let spacing = m[1].length + | |
m[2].length; | |
for (const factor of computeFactors(spacing)) { | |
if (factor in factors) { | |
factors[factor] += 1; | |
} else { | |
factors[factor] = 1; | |
} | |
} | |
} | |
delete factors[1]; | |
let bestLen = 1; | |
let bestScore = 1; | |
for (const len in factors) { | |
if (factors[len] > bestScore) { | |
bestScore = factors[len]; | |
bestLen = len; | |
} else if (factors[len] === bestScore && len > bestLen) { | |
bestLen = len; | |
} | |
} | |
return bestLen; | |
} | |
function solveVigenere (keylen, ciphertext) { | |
ciphertext = ciphertext | |
.replace(/[^a-z0-9]/gi, '') | |
.toUpperCase() | |
.split('') | |
.map((c) => c.charCodeAt() - 'A'.charCodeAt()); | |
let frequencies = []; | |
for (let i = 0; i < keylen; ++i) { | |
frequencies[i] = []; | |
for (let j = 0; j < 26; ++j) { | |
frequencies[i][j] = 0; | |
} | |
} | |
// count every letter | |
for (let i = 0; i < ciphertext.length; ++i) { | |
frequencies[i % keylen][ciphertext[i]] += 1; | |
} | |
// transform into frequencies | |
for (const frequency of frequencies) { | |
let total = 0; | |
for (let i = 0; i < 26; ++i) { | |
total += frequency[i]; | |
} | |
for (let i = 0; i < 26; ++i) { | |
frequency[i] /= total; | |
} | |
} | |
// find minimal error | |
let key = []; | |
for (const frequency of frequencies) { | |
let bestShift = 0; | |
let bestShiftScore = Infinity; | |
for (let shift = 0; shift < 26; ++shift) { | |
let shiftScore = 0; | |
for (let i = 0; i < 26; ++i) { | |
shiftScore += Math.abs(englishFrequency[i] - frequency[(i + shift) % 26]); | |
} | |
if (shiftScore < bestShiftScore) { | |
bestShiftScore = shiftScore; | |
bestShift = shift; | |
} | |
} | |
key.push(bestShift); | |
} | |
return String.fromCharCode.apply(null, key.map(c => 'A'.charCodeAt(0) + c)); | |
} | |
function computeFactors (num) { | |
let factors = []; | |
for (let i = 1; i <= Math.floor(Math.sqrt(num)); i += 1) { | |
if (num % i === 0) { | |
factors.push(i); | |
if (num / i !== i) { | |
factors.push(num / i); | |
} | |
} | |
} | |
factors.sort((a, b) => a - b);// numeric sort | |
return factors; | |
} | |
function decrypt (ciphertext, key) { | |
key = key.split('').map((c) => c.charCodeAt() - 'A'.charCodeAt()); | |
let k = 0; | |
for (let i = 0; i < ciphertext.length; ++i) { | |
if (/[^a-z0-9]/gi.test(ciphertext[i])) { | |
continue; | |
} | |
const shift = key[k++ % key.length]; | |
let letter = ciphertext[i].toUpperCase().charCodeAt() - 'A'.charCodeAt(); | |
letter = (letter + 26 - shift) % 26; | |
ciphertext = ciphertext.substr(0, i) + | |
String.fromCharCode(letter + 'A'.charCodeAt()) + ciphertext.substr(i + 1); | |
} | |
return ciphertext; | |
} | |
const ciphertext = fs.readFileSync(process.argv[2], 'utf-8'); | |
const keylen = kasikiExamination(ciphertext); | |
console.log(keylen); | |
const key = solveVigenere(keylen, ciphertext); | |
console.log(key); | |
console.log(decrypt(ciphertext, key)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment