UNPKG

zxcvbn

Version:

realistic password strength estimation

432 lines (422 loc) 12.6 kB
// 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