zxcvbn
Version:
realistic password strength estimation
697 lines (681 loc) • 21.6 kB
JavaScript
// Generated by CoffeeScript 1.10.0
var DATE_MAX_YEAR, DATE_MIN_YEAR, DATE_SPLITS, GRAPHS, L33T_TABLE, RANKED_DICTIONARIES, REGEXEN, adjacency_graphs, build_ranked_dict, frequency_lists, lst, matching, name, scoring;
frequency_lists = require('./frequency_lists');
adjacency_graphs = require('./adjacency_graphs');
scoring = require('./scoring');
build_ranked_dict = function(ordered_list) {
var i, len1, o, result, word;
result = {};
i = 1;
for (o = 0, len1 = ordered_list.length; o < len1; o++) {
word = ordered_list[o];
result[word] = i;
i += 1;
}
return result;
};
RANKED_DICTIONARIES = {};
for (name in frequency_lists) {
lst = frequency_lists[name];
RANKED_DICTIONARIES[name] = build_ranked_dict(lst);
}
GRAPHS = {
qwerty: adjacency_graphs.qwerty,
dvorak: adjacency_graphs.dvorak,
keypad: adjacency_graphs.keypad,
mac_keypad: adjacency_graphs.mac_keypad
};
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']
};
REGEXEN = {
recent_year: /19\d\d|200\d|201\d/g
};
DATE_MAX_YEAR = 2050;
DATE_MIN_YEAR = 1000;
DATE_SPLITS = {
4: [[1, 2], [2, 3]],
5: [[1, 3], [2, 3]],
6: [[1, 2], [2, 4], [4, 5]],
7: [[1, 3], [2, 3], [4, 5], [4, 6]],
8: [[2, 4], [4, 6]]
};
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 len1, o, ref, results;
ref = string.split('');
results = [];
for (o = 0, len1 = ref.length; o < len1; o++) {
chr = ref[o];
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 len1, matcher, matchers, matches, o;
matches = [];
matchers = [this.dictionary_match, this.reverse_dictionary_match, this.l33t_match, this.spatial_match, this.repeat_match, this.sequence_match, this.regex_match, this.date_match];
for (o = 0, len1 = matchers.length; o < len1; o++) {
matcher = matchers[o];
this.extend(matches, matcher.call(this, password));
}
return this.sorted(matches);
},
dictionary_match: function(password, _ranked_dictionaries) {
var dictionary_name, i, j, len, matches, o, p, 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 = o = 0, ref = len; 0 <= ref ? o < ref : o > ref; i = 0 <= ref ? ++o : --o) {
for (j = p = ref1 = i, ref2 = len; ref1 <= ref2 ? p < ref2 : p > ref2; j = ref1 <= ref2 ? ++p : --p) {
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,
reversed: false,
l33t: false
});
}
}
}
}
return this.sorted(matches);
},
reverse_dictionary_match: function(password, _ranked_dictionaries) {
var len1, match, matches, o, ref, reversed_password;
if (_ranked_dictionaries == null) {
_ranked_dictionaries = RANKED_DICTIONARIES;
}
reversed_password = password.split('').reverse().join('');
matches = this.dictionary_match(reversed_password, _ranked_dictionaries);
for (o = 0, len1 = matches.length; o < len1; o++) {
match = matches[o];
match.token = match.token.split('').reverse().join('');
match.reversed = true;
ref = [password.length - 1 - match.j, password.length - 1 - match.i], match.i = ref[0], match.j = ref[1];
}
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, len1, letter, o, password_chars, ref, relevant_subs, sub, subs, subtable;
password_chars = {};
ref = password.split('');
for (o = 0, len1 = ref.length; o < len1; o++) {
chr = ref[o];
password_chars[chr] = true;
}
subtable = {};
for (letter in table) {
subs = table[letter];
relevant_subs = (function() {
var len2, p, results;
results = [];
for (p = 0, len2 = subs.length; p < len2; p++) {
sub = subs[p];
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, l33t_chr, len1, len2, o, p, 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, label, len1, members, o, sub, v;
deduped = [];
members = {};
for (o = 0, len1 = subs.length; o < len1; o++) {
sub = subs[o];
assoc = (function() {
var len2, p, results;
results = [];
for (v = p = 0, len2 = sub.length; p < len2; v = ++p) {
k = sub[v];
results.push([k, v]);
}
return results;
})();
assoc.sort();
label = ((function() {
var len2, p, results;
results = [];
for (v = p = 0, len2 = assoc.length; p < len2; v = ++p) {
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, l33t_chr, len1, len2, next_subs, o, p, q, 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 (o = 0, len1 = ref.length; o < len1; o++) {
l33t_chr = ref[o];
for (p = 0, len2 = subs.length; p < len2; p++) {
sub = subs[p];
dup_l33t_index = -1;
for (i = q = 0, ref1 = sub.length; 0 <= ref1 ? q < ref1 : q > ref1; i = 0 <= ref1 ? ++q : --q) {
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 (o = 0, len1 = subs.length; o < len1; o++) {
sub = subs[o];
sub_dict = {};
for (p = 0, len2 = sub.length; p < len2; p++) {
ref = sub[p], 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, len1, len2, match, match_sub, matches, o, p, 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 (o = 0, len1 = ref.length; o < len1; o++) {
sub = ref[o];
if (this.empty(sub)) {
break;
}
subbed_password = this.translate(password, sub);
ref1 = this.dictionary_match(subbed_password, _ranked_dictionaries);
for (p = 0, len2 = ref1.length; p < len2; p++) {
match = ref1[p];
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.filter(function(match) {
return match.token.length > 1;
}));
},
spatial_match: function(password, _graphs) {
var graph, graph_name, matches;
if (_graphs == null) {
_graphs = GRAPHS;
}
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);
},
SHIFTED_RX: /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/,
spatial_match_helper: function(password, graph, graph_name) {
var adj, adjacents, cur_char, cur_direction, found, found_direction, i, j, last_direction, len1, matches, o, prev_char, shifted_count, turns;
matches = [];
i = 0;
while (i < password.length - 1) {
j = i + 1;
last_direction = null;
turns = 0;
if ((graph_name === 'qwerty' || graph_name === 'dvorak') && this.SHIFTED_RX.exec(password.charAt(i))) {
shifted_count = 1;
} else {
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 (o = 0, len1 = adjacents.length; o < len1; o++) {
adj = adjacents[o];
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 base_analysis, base_guesses, base_matches, base_token, greedy, greedy_match, i, j, lastIndex, lazy, lazy_anchored, lazy_match, match, matches, ref;
matches = [];
greedy = /(.+)\1+/g;
lazy = /(.+?)\1+/g;
lazy_anchored = /^(.+?)\1+$/;
lastIndex = 0;
while (lastIndex < password.length) {
greedy.lastIndex = lazy.lastIndex = lastIndex;
greedy_match = greedy.exec(password);
lazy_match = lazy.exec(password);
if (greedy_match == null) {
break;
}
if (greedy_match[0].length > lazy_match[0].length) {
match = greedy_match;
base_token = lazy_anchored.exec(match[0])[1];
} else {
match = lazy_match;
base_token = match[1];
}
ref = [match.index, match.index + match[0].length - 1], i = ref[0], j = ref[1];
base_analysis = scoring.most_guessable_match_sequence(base_token, this.omnimatch(base_token));
base_matches = base_analysis.sequence;
base_guesses = base_analysis.guesses;
matches.push({
pattern: 'repeat',
i: i,
j: j,
token: match[0],
base_token: base_token,
base_guesses: base_guesses,
base_matches: base_matches,
repeat_count: match[0].length / base_token.length
});
lastIndex = j + 1;
}
return matches;
},
MAX_DELTA: 5,
sequence_match: function(password) {
var delta, i, j, k, last_delta, o, ref, result, update;
if (password.length === 1) {
return [];
}
update = (function(_this) {
return function(i, j, delta) {
var ref, sequence_name, sequence_space, token;
if (j - i > 1 || Math.abs(delta) === 1) {
if ((0 < (ref = Math.abs(delta)) && ref <= _this.MAX_DELTA)) {
token = password.slice(i, +j + 1 || 9e9);
if (/^[a-z]+$/.test(token)) {
sequence_name = 'lower';
sequence_space = 26;
} else if (/^[A-Z]+$/.test(token)) {
sequence_name = 'upper';
sequence_space = 26;
} else if (/^\d+$/.test(token)) {
sequence_name = 'digits';
sequence_space = 10;
} else {
sequence_name = 'unicode';
sequence_space = 26;
}
return result.push({
pattern: 'sequence',
i: i,
j: j,
token: password.slice(i, +j + 1 || 9e9),
sequence_name: sequence_name,
sequence_space: sequence_space,
ascending: delta > 0
});
}
}
};
})(this);
result = [];
i = 0;
last_delta = null;
for (k = o = 1, ref = password.length; 1 <= ref ? o < ref : o > ref; k = 1 <= ref ? ++o : --o) {
delta = password.charCodeAt(k) - password.charCodeAt(k - 1);
if (last_delta == null) {
last_delta = delta;
}
if (delta === last_delta) {
continue;
}
j = k - 1;
update(i, j, last_delta);
i = j;
last_delta = delta;
}
update(i, password.length - 1, last_delta);
return result;
},
regex_match: function(password, _regexen) {
var matches, regex, rx_match, token;
if (_regexen == null) {
_regexen = REGEXEN;
}
matches = [];
for (name in _regexen) {
regex = _regexen[name];
regex.lastIndex = 0;
while (rx_match = regex.exec(password)) {
token = rx_match[0];
matches.push({
pattern: 'regex',
token: token,
i: rx_match.index,
j: rx_match.index + rx_match[0].length - 1,
regex_name: name,
regex_match: rx_match
});
}
}
return this.sorted(matches);
},
date_match: function(password) {
var best_candidate, candidate, candidates, distance, dmy, i, j, k, l, len1, len2, matches, maybe_date_no_separator, maybe_date_with_separator, metric, min_distance, o, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, rx_match, s, t, token;
matches = [];
maybe_date_no_separator = /^\d{4,8}$/;
maybe_date_with_separator = /^(\d{1,4})([\s\/\\_.-])(\d{1,2})\2(\d{1,4})$/;
for (i = o = 0, ref = password.length - 4; 0 <= ref ? o <= ref : o >= ref; i = 0 <= ref ? ++o : --o) {
for (j = p = ref1 = i + 3, ref2 = i + 7; ref1 <= ref2 ? p <= ref2 : p >= ref2; j = ref1 <= ref2 ? ++p : --p) {
if (j >= password.length) {
break;
}
token = password.slice(i, +j + 1 || 9e9);
if (!maybe_date_no_separator.exec(token)) {
continue;
}
candidates = [];
ref3 = DATE_SPLITS[token.length];
for (q = 0, len1 = ref3.length; q < len1; q++) {
ref4 = ref3[q], k = ref4[0], l = ref4[1];
dmy = this.map_ints_to_dmy([parseInt(token.slice(0, k)), parseInt(token.slice(k, l)), parseInt(token.slice(l))]);
if (dmy != null) {
candidates.push(dmy);
}
}
if (!(candidates.length > 0)) {
continue;
}
best_candidate = candidates[0];
metric = function(candidate) {
return Math.abs(candidate.year - scoring.REFERENCE_YEAR);
};
min_distance = metric(candidates[0]);
ref5 = candidates.slice(1);
for (r = 0, len2 = ref5.length; r < len2; r++) {
candidate = ref5[r];
distance = metric(candidate);
if (distance < min_distance) {
ref6 = [candidate, distance], best_candidate = ref6[0], min_distance = ref6[1];
}
}
matches.push({
pattern: 'date',
token: token,
i: i,
j: j,
separator: '',
year: best_candidate.year,
month: best_candidate.month,
day: best_candidate.day
});
}
}
for (i = s = 0, ref7 = password.length - 6; 0 <= ref7 ? s <= ref7 : s >= ref7; i = 0 <= ref7 ? ++s : --s) {
for (j = t = ref8 = i + 5, ref9 = i + 9; ref8 <= ref9 ? t <= ref9 : t >= ref9; j = ref8 <= ref9 ? ++t : --t) {
if (j >= password.length) {
break;
}
token = password.slice(i, +j + 1 || 9e9);
rx_match = maybe_date_with_separator.exec(token);
if (rx_match == null) {
continue;
}
dmy = this.map_ints_to_dmy([parseInt(rx_match[1]), parseInt(rx_match[3]), parseInt(rx_match[4])]);
if (dmy == null) {
continue;
}
matches.push({
pattern: 'date',
token: token,
i: i,
j: j,
separator: rx_match[2],
year: dmy.year,
month: dmy.month,
day: dmy.day
});
}
}
return this.sorted(matches.filter(function(match) {
var is_submatch, len3, other_match, u;
is_submatch = false;
for (u = 0, len3 = matches.length; u < len3; u++) {
other_match = matches[u];
if (match === other_match) {
continue;
}
if (other_match.i <= match.i && other_match.j >= match.j) {
is_submatch = true;
break;
}
}
return !is_submatch;
}));
},
map_ints_to_dmy: function(ints) {
var dm, int, len1, len2, len3, o, over_12, over_31, p, possible_year_splits, q, ref, ref1, rest, under_1, y;
if (ints[1] > 31 || ints[1] <= 0) {
return;
}
over_12 = 0;
over_31 = 0;
under_1 = 0;
for (o = 0, len1 = ints.length; o < len1; o++) {
int = ints[o];
if ((99 < int && int < DATE_MIN_YEAR) || int > DATE_MAX_YEAR) {
return;
}
if (int > 31) {
over_31 += 1;
}
if (int > 12) {
over_12 += 1;
}
if (int <= 0) {
under_1 += 1;
}
}
if (over_31 >= 2 || over_12 === 3 || under_1 >= 2) {
return;
}
possible_year_splits = [[ints[2], ints.slice(0, 2)], [ints[0], ints.slice(1, 3)]];
for (p = 0, len2 = possible_year_splits.length; p < len2; p++) {
ref = possible_year_splits[p], y = ref[0], rest = ref[1];
if ((DATE_MIN_YEAR <= y && y <= DATE_MAX_YEAR)) {
dm = this.map_ints_to_dm(rest);
if (dm != null) {
return {
year: y,
month: dm.month,
day: dm.day
};
} else {
return;
}
}
}
for (q = 0, len3 = possible_year_splits.length; q < len3; q++) {
ref1 = possible_year_splits[q], y = ref1[0], rest = ref1[1];
dm = this.map_ints_to_dm(rest);
if (dm != null) {
y = this.two_to_four_digit_year(y);
return {
year: y,
month: dm.month,
day: dm.day
};
}
}
},
map_ints_to_dm: function(ints) {
var d, len1, m, o, ref, ref1;
ref = [ints, ints.slice().reverse()];
for (o = 0, len1 = ref.length; o < len1; o++) {
ref1 = ref[o], d = ref1[0], m = ref1[1];
if ((1 <= d && d <= 31) && (1 <= m && m <= 12)) {
return {
day: d,
month: m
};
}
}
},
two_to_four_digit_year: function(year) {
if (year > 99) {
return year;
} else if (year > 50) {
return year + 1900;
} else {
return year + 2000;
}
}
};
module.exports = matching;
//# sourceMappingURL=matching.js.map