Skip to content

Instantly share code, notes, and snippets.

@LenweSaralonde
Created December 5, 2024 12:44
Show Gist options
  • Select an option

  • Save LenweSaralonde/4e05f253577d5e4c97f770c707c2acee to your computer and use it in GitHub Desktop.

Select an option

Save LenweSaralonde/4e05f253577d5e4c97f770c707c2acee to your computer and use it in GitHub Desktop.
Bruteforce vigenère cracking Node.js script
// Usage: node resolve.js
// You can change the parameters below
const puzzle = 'JYPFFQVY';
const errorThreshold = 1;
const dictionaryFile = 'All-English-and-WoW-words.txt'; // Get this file at https://gist.github.com/LenweSaralonde/4f9a98c6d7d2569cff881ad8a13a7c4a
/// ///////////////////////////////////
const fs = require('fs');
const charA = 'A'.charCodeAt(0);
function getLetterNumAt(str, pos) {
return str.charCodeAt(pos) - charA;
}
function getLetterFromNum(num) {
return String.fromCharCode(((26 + num) % 26) + charA);
}
function vigenereDecode(word, key, sign = -1) {
let decoded = '';
let keyPos = 0;
for (let i = 0; i < word.length; i++) {
if (word[i] === ' ') {
decoded += ' ';
} else {
decoded += getLetterFromNum(
getLetterNumAt(word, i) +
sign * getLetterNumAt(key, keyPos % key.length),
);
keyPos++;
}
}
return decoded;
}
function vigenereEncode(word, key) {
return vigenereDecode(word, key, 1);
}
/// ///////////////////////////////////
fs.readFile(dictionaryFile, 'utf8', (err, data) => {
console.log('Loading and sorting list...');
// Extract rows from text file, uppercase, no accents, remove spaces
const rows = data
.split('\n')
.map((word) =>
word
// Strip accents
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
// UPPERCASE
.toUpperCase()
// Keep letters only
.replace(/[^A-Z]+/g, '')
.trim(),
)
// Extract words having at least the length of the puzzle string
.filter((word) => word.length >= puzzle.length);
// Sort words alphabetically
const wordsMap = new Map();
for (const row of rows) {
const rowWords = row.split(' ');
for (const word of rowWords) {
if (!wordsMap.has(word)) {
wordsMap.set(word, word);
}
}
}
const words = [];
wordsMap.forEach((word) => word.length >= puzzle.length && words.push(word));
words.sort();
// Group words by length
const wordsByLength = new Map();
for (const word of words) {
if (!wordsByLength.has(word.length)) {
wordsByLength.set(word.length, []);
}
wordsByLength.get(word.length).push(word);
}
// Bruteforce
const checkWord = (algorithm, word, decodedWord) => {
const threshold = Math.ceil(
(errorThreshold * decodedWord.length) / puzzle.length,
);
for (const otherWord of wordsByLength.get(decodedWord.length)) {
let errors = 0;
for (let i = 0; i < decodedWord.length; i++) {
if (otherWord[i] !== decodedWord[i]) {
errors++;
}
}
if (errors <= threshold) {
console.log(
word,
'->',
otherWord,
`(${decodedWord} with ${errors} error(s))`,
`(${algorithm})`,
);
}
}
};
console.log(`${words.length} words found of ${puzzle.length}+ letters.`);
console.log('Using puzzle key', puzzle);
console.log('Error tolerance', errorThreshold);
console.log('Bruteforcing...');
words.forEach((word, index) => {
// Display progression
process.stdout.write(
`${Math.floor((10000 * index) / words.length) / 100} % \r`,
);
// Try to crack some stuff
checkWord(
'vigenereDecode(word, puzzle)',
word,
vigenereDecode(word, puzzle),
);
checkWord(
'vigenereDecode(puzzle, word)',
word,
vigenereDecode(puzzle, word),
);
checkWord(
'vigenereEncode(word, puzzle)',
word,
vigenereEncode(word, puzzle),
);
checkWord(
'vigenereEncode(puzzle, word)',
word,
vigenereEncode(puzzle, word),
);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment