Last active
September 15, 2017 19:43
-
-
Save bjouhier/94479643cf11a96d8bb320c55b0ddb3c to your computer and use it in GitHub Desktop.
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
"use strict"; | |
// experimental value | |
// nsyms is size of alphabet (62 for letters + digits) | |
// len is password length | |
// maxReapeat is the max number of repeats we allow | |
// returns the probability of a password reject. | |
function expe(nsyms, len, maxRepeat) { | |
// generate random array of len symbols | |
const gen = len => { | |
const vals = []; | |
for (let i = 0; i < len; i++) vals.push(Math.floor(nsyms * Math.random())); | |
return vals; | |
} | |
const accepts = vals => { | |
// sort the array of vals to easily find repeat groups | |
vals.sort((x, y) => x - y); | |
// iterate to find repeat groups | |
let old = -1, repeat = 0; | |
for (let i = 0; i < vals.length; i++) { | |
if (vals[i] === old) { | |
if (++repeat >= maxRepeat) return false; | |
} else { | |
repeat = 0; | |
} | |
old = vals[i]; | |
} | |
return true; | |
} | |
// run large number of iterations | |
const iterations = 1000000; | |
let accepted = 0; | |
for (let i = 0; i < iterations; i++) { | |
const vals = gen(len); | |
if (accepts(vals)) accepted++; | |
} | |
// rejected proba | |
return 1 - accepted / iterations; | |
} | |
// exact value | |
// same params and return as above | |
function theo(nsyms, len, maxRepeat) { | |
const fact = n => n > 1 ? n * fact(n - 1) : 1; | |
const anp = (n, p) => fact(n) / fact(n - p); | |
const cnp = (n, p) => anp(n, p) / fact(p); | |
// number of cases that do not contain any repeat group of size > maxRepeat | |
const repeating = (nsyms, len, maxRepeat) => { | |
// if maxRepeat is 1, easy, just anp | |
if (maxRepeat === 1) return anp(nsyms, len); | |
let total = 0; | |
// max number of groups of exactly maxRepeat identical syms | |
const max = Math.floor(len / maxRepeat); | |
// loop over number of possible groups | |
for (let ngroups = 0; ngroups <= max; ngroups++) { | |
let ncases = 1; | |
for (let i = 0; i < ngroups; i++) { | |
// for each group we need to choose the sym among nsyms - 1 | |
// and then choose the positions (a subset of maxRepeat in what remains) | |
ncases *= (nsyms - i) * cnp(len - maxRepeat * i, maxRepeat); | |
} | |
// divide by the number of permutations of the groups | |
ncases /= fact(ngroups); | |
// multiply by the number of cases with maxRepeat - 1 in the remaining positions. | |
ncases *= repeating(nsyms - ngroups, len - maxRepeat * ngroups, maxRepeat - 1); | |
// aggregate | |
total += ncases; | |
} | |
return total; | |
} | |
// rejected proba. | |
// total number of cases is just nsyms^len | |
return 1 - repeating(nsyms, len, maxRepeat) / Math.pow(nsyms, len); | |
} | |
// show the results | |
[1, 2, 3, 4, 5].forEach(maxRepeat => { | |
console.log('maxRepeat', maxRepeat); | |
[5, 10, 20, 30, 40, 50, 60].forEach(len => { | |
[expe, theo].forEach(f => { | |
const rounded = x => Math.round(x * 1000000) / 10000; | |
console.log('\t', f.name, 'len', len, '=>', rounded(f(62, len, maxRepeat)), '%'); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What are the odds of finding a character (chosen among
nsyms
chars) repeated more thanmaxRepeat
times in a random password of lengthlen
.See https://twitter.com/bjouhier/status/908390924526473217
Output: