UNPKG

zxcvbn

Version:

realistic password strength estimation

676 lines (665 loc) 21 kB
// Generated by CoffeeScript 1.9.3 var GRAPHS, L33T_TABLE, RANKED_DICTIONARIES, SEQUENCES, adjacency_graphs, build_ranked_dict, frequency_lists, matching, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; frequency_lists = require('./frequency_lists'); adjacency_graphs = require('./adjacency_graphs'); build_ranked_dict = function(ordered_list) { var i, l, len1, result, word; result = {}; i = 1; for (l = 0, len1 = ordered_list.length; l < len1; l++) { word = ordered_list[l]; result[word] = i; i += 1; } return result; }; RANKED_DICTIONARIES = { passwords: build_ranked_dict(frequency_lists.passwords), english: build_ranked_dict(frequency_lists.english), surnames: build_ranked_dict(frequency_lists.surnames), male_names: build_ranked_dict(frequency_lists.male_names), female_names: build_ranked_dict(frequency_lists.female_names) }; GRAPHS = { qwerty: adjacency_graphs.qwerty, dvorak: adjacency_graphs.dvorak, keypad: adjacency_graphs.keypad, mac_keypad: adjacency_graphs.mac_keypad }; SEQUENCES = { lower: 'abcdefghijklmnopqrstuvwxyz', upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', digits: '0123456789' }; L33T_TABLE = { a: ['4', '@'], b: ['8'], c: ['(', '{', '[', '<'], e: ['3'], g: ['6', '9'], i: ['1', '!', '|'], l: ['1', '|', '7'], o: ['0'], s: ['$', '5'], t: ['+', '7'], x: ['%'], z: ['2'] }; matching = { empty: function(obj) { var k; return ((function() { var results; results = []; for (k in obj) { results.push(k); } return results; })()).length === 0; }, extend: function(lst, lst2) { return lst.push.apply(lst, lst2); }, translate: function(string, chr_map) { var chr; return ((function() { var l, len1, ref, results; ref = string.split(''); results = []; for (l = 0, len1 = ref.length; l < len1; l++) { chr = ref[l]; results.push(chr_map[chr] || chr); } return results; })()).join(''); }, mod: function(n, m) { return ((n % m) + m) % m; }, sorted: function(matches) { return matches.sort(function(m1, m2) { return (m1.i - m2.i) || (m1.j - m2.j); }); }, omnimatch: function(password) { var l, len1, matcher, matchers, matches; matches = []; matchers = [this.dictionary_match, this.l33t_match, this.digits_match, this.year_match, this.date_match, this.repeat_match, this.sequence_match, this.spatial_match]; for (l = 0, len1 = matchers.length; l < len1; l++) { matcher = matchers[l]; this.extend(matches, matcher.call(this, password)); } return this.sorted(matches); }, dictionary_match: function(password, _ranked_dictionaries) { var dictionary_name, i, j, l, len, matches, o, password_lower, rank, ranked_dict, ref, ref1, ref2, word; if (_ranked_dictionaries == null) { _ranked_dictionaries = RANKED_DICTIONARIES; } matches = []; len = password.length; password_lower = password.toLowerCase(); for (dictionary_name in _ranked_dictionaries) { ranked_dict = _ranked_dictionaries[dictionary_name]; for (i = l = 0, ref = len; 0 <= ref ? l < ref : l > ref; i = 0 <= ref ? ++l : --l) { for (j = o = ref1 = i, ref2 = len; ref1 <= ref2 ? o < ref2 : o > ref2; j = ref1 <= ref2 ? ++o : --o) { if (password_lower.slice(i, +j + 1 || 9e9) in ranked_dict) { word = password_lower.slice(i, +j + 1 || 9e9); rank = ranked_dict[word]; matches.push({ pattern: 'dictionary', i: i, j: j, token: password.slice(i, +j + 1 || 9e9), matched_word: word, rank: rank, dictionary_name: dictionary_name }); } } } } return this.sorted(matches); }, set_user_input_dictionary: function(ordered_list) { return RANKED_DICTIONARIES['user_inputs'] = build_ranked_dict(ordered_list.slice()); }, relevant_l33t_subtable: function(password, table) { var chr, l, len1, letter, password_chars, ref, relevant_subs, sub, subs, subtable; password_chars = {}; ref = password.split(''); for (l = 0, len1 = ref.length; l < len1; l++) { chr = ref[l]; password_chars[chr] = true; } subtable = {}; for (letter in table) { subs = table[letter]; relevant_subs = (function() { var len2, o, results; results = []; for (o = 0, len2 = subs.length; o < len2; o++) { sub = subs[o]; if (sub in password_chars) { results.push(sub); } } return results; })(); if (relevant_subs.length > 0) { subtable[letter] = relevant_subs; } } return subtable; }, enumerate_l33t_subs: function(table) { var chr, dedup, helper, k, keys, l, l33t_chr, len1, len2, o, ref, sub, sub_dict, sub_dicts, subs; keys = (function() { var results; results = []; for (k in table) { results.push(k); } return results; })(); subs = [[]]; dedup = function(subs) { var assoc, deduped, l, label, len1, members, sub, v; deduped = []; members = {}; for (l = 0, len1 = subs.length; l < len1; l++) { sub = subs[l]; assoc = (function() { var len2, o, results; results = []; for (v = o = 0, len2 = sub.length; o < len2; v = ++o) { k = sub[v]; results.push([k, v]); } return results; })(); assoc.sort(); label = ((function() { var len2, o, results; results = []; for (v = o = 0, len2 = assoc.length; o < len2; v = ++o) { k = assoc[v]; results.push(k + ',' + v); } return results; })()).join('-'); if (!(label in members)) { members[label] = true; deduped.push(sub); } } return deduped; }; helper = function(keys) { var dup_l33t_index, first_key, i, l, l33t_chr, len1, len2, next_subs, o, p, ref, ref1, rest_keys, sub, sub_alternative, sub_extension; if (!keys.length) { return; } first_key = keys[0]; rest_keys = keys.slice(1); next_subs = []; ref = table[first_key]; for (l = 0, len1 = ref.length; l < len1; l++) { l33t_chr = ref[l]; for (o = 0, len2 = subs.length; o < len2; o++) { sub = subs[o]; dup_l33t_index = -1; for (i = p = 0, ref1 = sub.length; 0 <= ref1 ? p < ref1 : p > ref1; i = 0 <= ref1 ? ++p : --p) { if (sub[i][0] === l33t_chr) { dup_l33t_index = i; break; } } if (dup_l33t_index === -1) { sub_extension = sub.concat([[l33t_chr, first_key]]); next_subs.push(sub_extension); } else { sub_alternative = sub.slice(0); sub_alternative.splice(dup_l33t_index, 1); sub_alternative.push([l33t_chr, first_key]); next_subs.push(sub); next_subs.push(sub_alternative); } } } subs = dedup(next_subs); return helper(rest_keys); }; helper(keys); sub_dicts = []; for (l = 0, len1 = subs.length; l < len1; l++) { sub = subs[l]; sub_dict = {}; for (o = 0, len2 = sub.length; o < len2; o++) { ref = sub[o], l33t_chr = ref[0], chr = ref[1]; sub_dict[l33t_chr] = chr; } sub_dicts.push(sub_dict); } return sub_dicts; }, l33t_match: function(password, _ranked_dictionaries, _l33t_table) { var chr, k, l, len1, len2, match, match_sub, matches, o, ref, ref1, sub, subbed_chr, subbed_password, token, v; if (_ranked_dictionaries == null) { _ranked_dictionaries = RANKED_DICTIONARIES; } if (_l33t_table == null) { _l33t_table = L33T_TABLE; } matches = []; ref = this.enumerate_l33t_subs(this.relevant_l33t_subtable(password, _l33t_table)); for (l = 0, len1 = ref.length; l < len1; l++) { sub = ref[l]; if (this.empty(sub)) { break; } subbed_password = this.translate(password, sub); ref1 = this.dictionary_match(subbed_password, _ranked_dictionaries); for (o = 0, len2 = ref1.length; o < len2; o++) { match = ref1[o]; token = password.slice(match.i, +match.j + 1 || 9e9); if (token.toLowerCase() === match.matched_word) { continue; } match_sub = {}; for (subbed_chr in sub) { chr = sub[subbed_chr]; if (token.indexOf(subbed_chr) !== -1) { match_sub[subbed_chr] = chr; } } match.l33t = true; match.token = token; match.sub = match_sub; match.sub_display = ((function() { var results; results = []; for (k in match_sub) { v = match_sub[k]; results.push(k + " -> " + v); } return results; })()).join(', '); matches.push(match); } } return this.sorted(matches); }, spatial_match: function(password) { var graph, graph_name, matches; matches = []; for (graph_name in GRAPHS) { graph = GRAPHS[graph_name]; this.extend(matches, this.spatial_match_helper(password, graph, graph_name)); } return this.sorted(matches); }, spatial_match_helper: function(password, graph, graph_name) { var adj, adjacents, cur_char, cur_direction, found, found_direction, i, j, l, last_direction, len1, matches, prev_char, shifted_count, turns; matches = []; i = 0; while (i < password.length - 1) { j = i + 1; last_direction = null; turns = 0; shifted_count = 0; while (true) { prev_char = password.charAt(j - 1); found = false; found_direction = -1; cur_direction = -1; adjacents = graph[prev_char] || []; if (j < password.length) { cur_char = password.charAt(j); for (l = 0, len1 = adjacents.length; l < len1; l++) { adj = adjacents[l]; cur_direction += 1; if (adj && adj.indexOf(cur_char) !== -1) { found = true; found_direction = cur_direction; if (adj.indexOf(cur_char) === 1) { shifted_count += 1; } if (last_direction !== found_direction) { turns += 1; last_direction = found_direction; } break; } } } if (found) { j += 1; } else { if (j - i > 2) { matches.push({ pattern: 'spatial', i: i, j: j - 1, token: password.slice(i, j), graph: graph_name, turns: turns, shifted_count: shifted_count }); } i = j; break; } } } return matches; }, repeat_match: function(password) { var cur_char, i, j, matches, min_repeat_length, prev_char, ref; min_repeat_length = 3; matches = []; i = 0; while (i < password.length) { j = i + 1; while (true) { ref = password.slice(j - 1, +j + 1 || 9e9), prev_char = ref[0], cur_char = ref[1]; if (password.charAt(j - 1) === password.charAt(j)) { j += 1; } else { j -= 1; if (j - i + 1 >= min_repeat_length) { matches.push({ pattern: 'repeat', i: i, j: j, token: password.slice(i, +j + 1 || 9e9), repeated_char: password.charAt(i) }); } break; } } i = j + 1; } return this.sorted(matches); }, sequence_match: function(password) { var direction, i, j, l, len1, matches, min_sequence_length, next_sequence_position, ref, ref1, sequence, sequence_name, sequence_position; min_sequence_length = 3; matches = []; for (sequence_name in SEQUENCES) { sequence = SEQUENCES[sequence_name]; ref = [1, -1]; for (l = 0, len1 = ref.length; l < len1; l++) { direction = ref[l]; i = 0; while (i < password.length) { if (ref1 = password[i], indexOf.call(sequence, ref1) < 0) { i += 1; continue; } j = i + 1; sequence_position = sequence.indexOf(password[i]); while (j < password.length) { next_sequence_position = this.mod(sequence_position + direction, sequence.length); if (sequence.indexOf(password[j]) !== next_sequence_position) { break; } j += 1; sequence_position = next_sequence_position; } j -= 1; if (j - i + 1 >= min_sequence_length) { matches.push({ pattern: 'sequence', i: i, j: j, token: password.slice(i, +j + 1 || 9e9), sequence_name: sequence_name, sequence_space: sequence.length, ascending: direction === 1 }); } i = j + 1; } } } return this.sorted(matches); }, repeat: function(chr, n) { var i; return ((function() { var l, ref, results; results = []; for (i = l = 1, ref = n; 1 <= ref ? l <= ref : l >= ref; i = 1 <= ref ? ++l : --l) { results.push(chr); } return results; })()).join(''); }, findall: function(password, rx) { var match, matches; matches = []; while (true) { match = password.match(rx); if (!match) { break; } match.i = match.index; match.j = match.index + match[0].length - 1; matches.push(match); password = password.replace(match[0], this.repeat(' ', match[0].length)); } return this.sorted(matches); }, digits_rx: /\d{3,}/, digits_match: function(password) { var i, j, l, len1, match, ref, ref1, results; ref = this.findall(password, this.digits_rx); results = []; for (l = 0, len1 = ref.length; l < len1; l++) { match = ref[l]; ref1 = [match.i, match.j], i = ref1[0], j = ref1[1]; results.push({ pattern: 'digits', i: i, j: j, token: password.slice(i, +j + 1 || 9e9) }); } return results; }, year_rx: /19\d\d|200\d|201\d/, year_match: function(password) { var i, j, l, len1, match, ref, ref1, results; ref = this.findall(password, this.year_rx); results = []; for (l = 0, len1 = ref.length; l < len1; l++) { match = ref[l]; ref1 = [match.i, match.j], i = ref1[0], j = ref1[1]; results.push({ pattern: 'year', i: i, j: j, token: password.slice(i, +j + 1 || 9e9) }); } return results; }, date_match: function(password) { return this.date_without_sep_match(password).concat(this.date_sep_match(password)); }, date_without_sep_match: function(password) { var candidate, candidates_round_1, candidates_round_2, date_matches, day, digit_match, end, i, j, l, len1, len2, len3, month, o, p, ref, ref1, ref2, ref3, token, valid, year; date_matches = []; ref = this.findall(password, /\d{4,8}/); for (l = 0, len1 = ref.length; l < len1; l++) { digit_match = ref[l]; ref1 = [digit_match.i, digit_match.j], i = ref1[0], j = ref1[1]; token = password.slice(i, +j + 1 || 9e9); end = token.length; candidates_round_1 = []; if (token.length <= 6) { candidates_round_1.push({ daymonth: token.slice(2), year: token.slice(0, 2), i: i, j: j }); candidates_round_1.push({ daymonth: token.slice(0, end - 2), year: token.slice(end - 2), i: i, j: j }); } if (token.length >= 6) { candidates_round_1.push({ daymonth: token.slice(4), year: token.slice(0, 4), i: i, j: j }); candidates_round_1.push({ daymonth: token.slice(0, end - 4), year: token.slice(end - 4), i: i, j: j }); } candidates_round_2 = []; for (o = 0, len2 = candidates_round_1.length; o < len2; o++) { candidate = candidates_round_1[o]; switch (candidate.daymonth.length) { case 2: candidates_round_2.push({ day: candidate.daymonth[0], month: candidate.daymonth[1], year: candidate.year, i: candidate.i, j: candidate.j }); break; case 3: candidates_round_2.push({ day: candidate.daymonth.slice(0, 2), month: candidate.daymonth[2], year: candidate.year, i: candidate.i, j: candidate.j }); candidates_round_2.push({ day: candidate.daymonth[0], month: candidate.daymonth.slice(1, 3), year: candidate.year, i: candidate.i, j: candidate.j }); break; case 4: candidates_round_2.push({ day: candidate.daymonth.slice(0, 2), month: candidate.daymonth.slice(2, 4), year: candidate.year, i: candidate.i, j: candidate.j }); } } for (p = 0, len3 = candidates_round_2.length; p < len3; p++) { candidate = candidates_round_2[p]; day = parseInt(candidate.day); month = parseInt(candidate.month); year = parseInt(candidate.year); ref2 = this.check_date(day, month, year), valid = ref2[0], (ref3 = ref2[1], day = ref3[0], month = ref3[1], year = ref3[2]); if (!valid) { continue; } date_matches.push({ pattern: 'date', i: candidate.i, j: candidate.j, token: password.slice(i, +j + 1 || 9e9), separator: '', day: day, month: month, year: year }); } } return date_matches; }, date_rx_year_suffix: /(\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})\2(19\d{2}|200\d|201\d|\d{2})/, date_rx_year_prefix: /(19\d{2}|200\d|201\d|\d{2})(\s|-|\/|\\|_|\.)(\d{1,2})\2(\d{1,2})/, date_sep_match: function(password) { var day, k, l, len1, len2, len3, match, matches, month, o, p, ref, ref1, ref2, ref3, ref4, ref5, results, valid, year; matches = []; ref = this.findall(password, this.date_rx_year_suffix); for (l = 0, len1 = ref.length; l < len1; l++) { match = ref[l]; ref1 = (function() { var len2, o, ref1, results; ref1 = [1, 3, 4]; results = []; for (o = 0, len2 = ref1.length; o < len2; o++) { k = ref1[o]; results.push(parseInt(match[k])); } return results; })(), match.day = ref1[0], match.month = ref1[1], match.year = ref1[2]; match.sep = match[2]; matches.push(match); } ref2 = this.findall(password, this.date_rx_year_prefix); for (o = 0, len2 = ref2.length; o < len2; o++) { match = ref2[o]; ref3 = (function() { var len3, p, ref3, results; ref3 = [4, 3, 1]; results = []; for (p = 0, len3 = ref3.length; p < len3; p++) { k = ref3[p]; results.push(parseInt(match[k])); } return results; })(), match.day = ref3[0], match.month = ref3[1], match.year = ref3[2]; match.sep = match[2]; matches.push(match); } results = []; for (p = 0, len3 = matches.length; p < len3; p++) { match = matches[p]; ref4 = this.check_date(match.day, match.month, match.year), valid = ref4[0], (ref5 = ref4[1], day = ref5[0], month = ref5[1], year = ref5[2]); if (!valid) { continue; } results.push({ pattern: 'date', i: match.i, j: match.j, token: password.slice(match.i, +match.j + 1 || 9e9), separator: match.sep, day: day, month: month, year: year }); } return results; }, check_date: function(day, month, year) { var ref; if ((12 <= month && month <= 31) && day <= 12) { ref = [month, day], day = ref[0], month = ref[1]; } if (day > 31 || month > 12) { return [false, []]; } if (!((1900 <= year && year <= 2019))) { return [false, []]; } return [true, [day, month, year]]; } }; module.exports = matching; //# sourceMappingURL=matching.js.map