Skip to content

Instantly share code, notes, and snippets.

@jasoncrawford
Created November 5, 2016 00:48
Show Gist options
  • Save jasoncrawford/7ffe570b7b20626c477aaaefa05e4082 to your computer and use it in GitHub Desktop.
Save jasoncrawford/7ffe570b7b20626c477aaaefa05e4082 to your computer and use it in GitHub Desktop.
XKCD-style password generator
// Generates “correct horse battery staple”-style password: xkcd.com/936
// Usage: node pwdgen.js <file>
// File should contain common words, one per line.
// Params:
var minWordLength = 4;
var desiredEntropyBits = 30;
var fs = require('fs');
var crypto = require('crypto');
var ln2 = Math.log(2);
function log2(x) {
return Math.log(x) / ln2;
}
function randomInt(n) {
var bitsNeeded = log2(n);
var bytesNeeded = Math.ceil(bitsNeeded/8);
var maxGenerated = Math.pow(2, 8*bytesNeeded); // biggest number we'll generate from random bytes (actually + 1)
var maxAllowed = Math.floor(maxGenerated/n) * n; // largest multiple of n <= maxGenerated
for (var i = 0; i < 100; i++) {
var buffer = crypto.randomBytes(bytesNeeded);
var bytes = Array.prototype.slice.call(buffer, 0); // turns buffer into array
var number = bytes.reduce(function (num, byte) { return num * 256 + byte; }, 0);
if (number < maxAllowed) return number % n;
}
throw new Error('crap');
}
function chooseWord(words) {
var i = randomInt(words.length);
return words[i];
}
function makePassword(words, desiredEntropyBits) {
var chosenWords = []
var entropyBitsPerWord = log2(words.length);
var wordsNeeded = Math.ceil(desiredEntropyBits / entropyBitsPerWord);
console.log(words.length + ' words = ' + entropyBitsPerWord + ' bits of entropy per word, ' + wordsNeeded + ' words needed');
for (var i = 0; i < wordsNeeded; i++) {
chosenWords.push(chooseWord(words));
}
return chosenWords.join(' ');
}
var filename = process.argv[2];
var contents = fs.readFileSync(filename, 'utf8');
var allWords = contents.split(/\s+/);
var words = allWords.filter(function (word) { return word.length >= minWordLength && word.match(/^\w+$/); });
console.log(allWords.length + ' words in ' + filename + ', ' + words.length + ' have all word characters and min length of ' + minWordLength);
var password = makePassword(words, desiredEntropyBits);
console.log(desiredEntropyBits + '-bit (or greater) password: ' + password);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment