Created
November 5, 2016 00:48
-
-
Save jasoncrawford/7ffe570b7b20626c477aaaefa05e4082 to your computer and use it in GitHub Desktop.
XKCD-style password generator
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
// 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