zxcvbn
Version:
realistic password strength estimation
432 lines (422 loc) • 12.6 kB
JavaScript
// Generated by CoffeeScript 1.9.3
var KEYBOARD_AVERAGE_DEGREE, KEYBOARD_STARTING_POSITIONS, KEYPAD_AVERAGE_DEGREE, KEYPAD_STARTING_POSITIONS, adjacency_graphs, calc_average_degree, k, scoring, v;
adjacency_graphs = require('./adjacency_graphs');
calc_average_degree = function(graph) {
var average, k, key, n, neighbors, v;
average = 0;
for (key in graph) {
neighbors = graph[key];
average += ((function() {
var l, len, results;
results = [];
for (l = 0, len = neighbors.length; l < len; l++) {
n = neighbors[l];
if (n) {
results.push(n);
}
}
return results;
})()).length;
}
average /= ((function() {
var results;
results = [];
for (k in graph) {
v = graph[k];
results.push(k);
}
return results;
})()).length;
return average;
};
KEYBOARD_AVERAGE_DEGREE = calc_average_degree(adjacency_graphs.qwerty);
KEYPAD_AVERAGE_DEGREE = calc_average_degree(adjacency_graphs.keypad);
KEYBOARD_STARTING_POSITIONS = ((function() {
var ref, results;
ref = adjacency_graphs.qwerty;
results = [];
for (k in ref) {
v = ref[k];
results.push(k);
}
return results;
})()).length;
KEYPAD_STARTING_POSITIONS = ((function() {
var ref, results;
ref = adjacency_graphs.keypad;
results = [];
for (k in ref) {
v = ref[k];
results.push(k);
}
return results;
})()).length;
scoring = {
nCk: function(n, k) {
var d, l, r, ref;
if (k > n) {
return 0;
}
if (k === 0) {
return 1;
}
r = 1;
for (d = l = 1, ref = k; 1 <= ref ? l <= ref : l >= ref; d = 1 <= ref ? ++l : --l) {
r *= n;
r /= d;
n -= 1;
}
return r;
},
lg: function(n) {
return Math.log(n) / Math.log(2);
},
minimum_entropy_match_sequence: function(password, matches) {
var backpointers, bruteforce_cardinality, candidate_entropy, crack_time, i, j, l, len, len1, m, make_bruteforce_match, match, match_sequence, match_sequence_copy, min_entropy, o, ref, ref1, ref2, up_to_k;
bruteforce_cardinality = this.calc_bruteforce_cardinality(password);
up_to_k = [];
backpointers = [];
for (k = l = 0, ref = password.length; 0 <= ref ? l < ref : l > ref; k = 0 <= ref ? ++l : --l) {
up_to_k[k] = (up_to_k[k - 1] || 0) + this.lg(bruteforce_cardinality);
backpointers[k] = null;
for (m = 0, len = matches.length; m < len; m++) {
match = matches[m];
if (!(match.j === k)) {
continue;
}
ref1 = [match.i, match.j], i = ref1[0], j = ref1[1];
candidate_entropy = (up_to_k[i - 1] || 0) + this.calc_entropy(match);
if (candidate_entropy < up_to_k[j]) {
up_to_k[j] = candidate_entropy;
backpointers[j] = match;
}
}
}
match_sequence = [];
k = password.length - 1;
while (k >= 0) {
match = backpointers[k];
if (match) {
match_sequence.push(match);
k = match.i - 1;
} else {
k -= 1;
}
}
match_sequence.reverse();
make_bruteforce_match = (function(_this) {
return function(i, j) {
return {
pattern: 'bruteforce',
i: i,
j: j,
token: password.slice(i, +j + 1 || 9e9),
entropy: _this.lg(Math.pow(bruteforce_cardinality, j - i + 1)),
cardinality: bruteforce_cardinality
};
};
})(this);
k = 0;
match_sequence_copy = [];
for (o = 0, len1 = match_sequence.length; o < len1; o++) {
match = match_sequence[o];
ref2 = [match.i, match.j], i = ref2[0], j = ref2[1];
if (i - k > 0) {
match_sequence_copy.push(make_bruteforce_match(k, i - 1));
}
k = j + 1;
match_sequence_copy.push(match);
}
if (k < password.length) {
match_sequence_copy.push(make_bruteforce_match(k, password.length - 1));
}
match_sequence = match_sequence_copy;
min_entropy = up_to_k[password.length - 1] || 0;
crack_time = this.entropy_to_crack_time(min_entropy);
return {
password: password,
entropy: this.round_to_x_digits(min_entropy, 3),
match_sequence: match_sequence,
crack_time: this.round_to_x_digits(crack_time, 3),
crack_time_display: this.display_time(crack_time),
score: this.crack_time_to_score(crack_time)
};
},
round_to_x_digits: function(n, x) {
return Math.round(n * Math.pow(10, x)) / Math.pow(10, x);
},
SECONDS_PER_GUESS: .010 / 100,
entropy_to_crack_time: function(entropy) {
return .5 * Math.pow(2, entropy) * this.SECONDS_PER_GUESS;
},
crack_time_to_score: function(seconds) {
if (seconds < Math.pow(10, 2)) {
return 0;
}
if (seconds < Math.pow(10, 4)) {
return 1;
}
if (seconds < Math.pow(10, 6)) {
return 2;
}
if (seconds < Math.pow(10, 8)) {
return 3;
}
return 4;
},
calc_entropy: function(match) {
var entropy_func;
if (match.entropy != null) {
return match.entropy;
}
entropy_func = (function() {
switch (match.pattern) {
case 'repeat':
return this.repeat_entropy;
case 'sequence':
return this.sequence_entropy;
case 'digits':
return this.digits_entropy;
case 'year':
return this.year_entropy;
case 'date':
return this.date_entropy;
case 'spatial':
return this.spatial_entropy;
case 'dictionary':
return this.dictionary_entropy;
}
}).call(this);
return match.entropy = entropy_func.call(this, match);
},
repeat_entropy: function(match) {
var cardinality;
cardinality = this.calc_bruteforce_cardinality(match.token);
return this.lg(cardinality * match.token.length);
},
sequence_entropy: function(match) {
var base_entropy, first_chr;
first_chr = match.token.charAt(0);
if (first_chr === 'a' || first_chr === '1') {
base_entropy = 1;
} else {
if (first_chr.match(/\d/)) {
base_entropy = this.lg(10);
} else if (first_chr.match(/[a-z]/)) {
base_entropy = this.lg(26);
} else {
base_entropy = this.lg(26) + 1;
}
}
if (!match.ascending) {
base_entropy += 1;
}
return base_entropy + this.lg(match.token.length);
},
digits_entropy: function(match) {
return this.lg(Math.pow(10, match.token.length));
},
NUM_YEARS: 119,
NUM_MONTHS: 12,
NUM_DAYS: 31,
year_entropy: function(match) {
return this.lg(this.NUM_YEARS);
},
date_entropy: function(match) {
var entropy;
if (match.year < 100) {
entropy = this.lg(this.NUM_DAYS * this.NUM_MONTHS * 100);
} else {
entropy = this.lg(this.NUM_DAYS * this.NUM_MONTHS * this.NUM_YEARS);
}
if (match.separator) {
entropy += 2;
}
return entropy;
},
spatial_entropy: function(match) {
var L, S, U, d, entropy, i, j, l, m, o, possibilities, possible_turns, ref, ref1, ref2, ref3, s, t;
if ((ref = match.graph) === 'qwerty' || ref === 'dvorak') {
s = KEYBOARD_STARTING_POSITIONS;
d = KEYBOARD_AVERAGE_DEGREE;
} else {
s = KEYPAD_STARTING_POSITIONS;
d = KEYPAD_AVERAGE_DEGREE;
}
possibilities = 0;
L = match.token.length;
t = match.turns;
for (i = l = 2, ref1 = L; 2 <= ref1 ? l <= ref1 : l >= ref1; i = 2 <= ref1 ? ++l : --l) {
possible_turns = Math.min(t, i - 1);
for (j = m = 1, ref2 = possible_turns; 1 <= ref2 ? m <= ref2 : m >= ref2; j = 1 <= ref2 ? ++m : --m) {
possibilities += this.nCk(i - 1, j - 1) * s * Math.pow(d, j);
}
}
entropy = this.lg(possibilities);
if (match.shifted_count) {
S = match.shifted_count;
U = match.token.length - match.shifted_count;
possibilities = 0;
for (i = o = 0, ref3 = Math.min(S, U); 0 <= ref3 ? o <= ref3 : o >= ref3; i = 0 <= ref3 ? ++o : --o) {
possibilities += this.nCk(S + U, i);
}
entropy += this.lg(possibilities);
}
return entropy;
},
dictionary_entropy: function(match) {
match.base_entropy = this.lg(match.rank);
match.uppercase_entropy = this.extra_uppercase_entropy(match);
match.l33t_entropy = this.extra_l33t_entropy(match);
return match.base_entropy + match.uppercase_entropy + match.l33t_entropy;
},
START_UPPER: /^[A-Z][^A-Z]+$/,
END_UPPER: /^[^A-Z]+[A-Z]$/,
ALL_UPPER: /^[^a-z]+$/,
ALL_LOWER: /^[^A-Z]+$/,
extra_uppercase_entropy: function(match) {
var L, U, chr, i, l, len, m, possibilities, ref, ref1, regex, word;
word = match.token;
if (word.match(this.ALL_LOWER)) {
return 0;
}
ref = [this.START_UPPER, this.END_UPPER, this.ALL_UPPER];
for (l = 0, len = ref.length; l < len; l++) {
regex = ref[l];
if (word.match(regex)) {
return 1;
}
}
U = ((function() {
var len1, m, ref1, results;
ref1 = word.split('');
results = [];
for (m = 0, len1 = ref1.length; m < len1; m++) {
chr = ref1[m];
if (chr.match(/[A-Z]/)) {
results.push(chr);
}
}
return results;
})()).length;
L = ((function() {
var len1, m, ref1, results;
ref1 = word.split('');
results = [];
for (m = 0, len1 = ref1.length; m < len1; m++) {
chr = ref1[m];
if (chr.match(/[a-z]/)) {
results.push(chr);
}
}
return results;
})()).length;
possibilities = 0;
for (i = m = 0, ref1 = Math.min(U, L); 0 <= ref1 ? m <= ref1 : m >= ref1; i = 0 <= ref1 ? ++m : --m) {
possibilities += this.nCk(U + L, i);
}
return this.lg(possibilities);
},
extra_l33t_entropy: function(match) {
var S, U, chr, i, l, possibilities, ref, ref1, subbed, unsubbed;
if (!match.l33t) {
return 0;
}
possibilities = 0;
ref = match.sub;
for (subbed in ref) {
unsubbed = ref[subbed];
S = ((function() {
var l, len, ref1, results;
ref1 = match.token.split('');
results = [];
for (l = 0, len = ref1.length; l < len; l++) {
chr = ref1[l];
if (chr === subbed) {
results.push(chr);
}
}
return results;
})()).length;
U = ((function() {
var l, len, ref1, results;
ref1 = match.token.split('');
results = [];
for (l = 0, len = ref1.length; l < len; l++) {
chr = ref1[l];
if (chr === unsubbed) {
results.push(chr);
}
}
return results;
})()).length;
for (i = l = 0, ref1 = Math.min(U, S); 0 <= ref1 ? l <= ref1 : l >= ref1; i = 0 <= ref1 ? ++l : --l) {
possibilities += this.nCk(U + S, i);
}
}
return this.lg(possibilities) || 1;
},
calc_bruteforce_cardinality: function(password) {
var c, chr, digits, l, len, lower, ord, ref, ref1, symbols, unicode, upper;
ref = [false, false, false, false, false], lower = ref[0], upper = ref[1], digits = ref[2], symbols = ref[3], unicode = ref[4];
ref1 = password.split('');
for (l = 0, len = ref1.length; l < len; l++) {
chr = ref1[l];
ord = chr.charCodeAt(0);
if ((0x30 <= ord && ord <= 0x39)) {
digits = true;
} else if ((0x41 <= ord && ord <= 0x5a)) {
upper = true;
} else if ((0x61 <= ord && ord <= 0x7a)) {
lower = true;
} else if (ord <= 0x7f) {
symbols = true;
} else {
unicode = true;
}
}
c = 0;
if (digits) {
c += 10;
}
if (upper) {
c += 26;
}
if (lower) {
c += 26;
}
if (symbols) {
c += 33;
}
if (unicode) {
c += 100;
}
return c;
},
display_time: function(seconds) {
var century, day, hour, minute, month, year;
minute = 60;
hour = minute * 60;
day = hour * 24;
month = day * 31;
year = month * 12;
century = year * 100;
if (seconds < minute) {
return 'instant';
} else if (seconds < hour) {
return (1 + Math.ceil(seconds / minute)) + " minutes";
} else if (seconds < day) {
return (1 + Math.ceil(seconds / hour)) + " hours";
} else if (seconds < month) {
return (1 + Math.ceil(seconds / day)) + " days";
} else if (seconds < year) {
return (1 + Math.ceil(seconds / month)) + " months";
} else if (seconds < century) {
return (1 + Math.ceil(seconds / year)) + " years";
} else {
return 'centuries';
}
}
};
module.exports = scoring;
//# sourceMappingURL=scoring.js.map