zxcvbn
Version:
realistic password strength estimation
676 lines (665 loc) • 21 kB
JavaScript
// 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