Archived
1
0
This repository has been archived on 2020-12-10. You can view files and clone it, but cannot push or open issues or pull requests.
old/assets/js/lib/password-score.js
2018-10-16 18:28:42 +02:00

1146 lines
44 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) 2013 - 2014 David Stutz
* License: https://github.com/davidstutz/password-score
*/
/**
* Represents a keyboard for checking adjacency on.
*
* @param {object} object
* @param {number} average
*/
function Keyboard(object, average) {
this.keyboard = object;
this.averageNeighbours = average;
this.areAdjacent = function(a, b) {
if (a in this.keyboard) {
for (var i = 0; i < this.keyboard[a].length; i++) {
if (this.keyboard[a][i] !== null && this.keyboard[a][i].indexOf(b) >= 0) {
return true;
}
}
}
return false;
};
};
/**
* Constructor.
*
* @param {string} password
*/
function Score(password) {
this.password = password;
}
/**
* Used to estimate the score of the given password.
*
* @class
* @author David Stutz
*/
Score.prototype = {
constructor: Score,
LOWER: 26,
UPPER: 26,
NUMBER: 10,
PUNCTUATION: 34,
DAYS: 31,
MONTHS: 31,
YEARS: 2000,
sequences: {
lower: 'abcdefghijklmnopqrstuvwxyz',
numbers: '01234567890'
},
leet: {
'1': ['i', 'l'],
'2': ['n', 'r', 'z'],
'3': ['e'],
'4': ['a'],
'5': ['s'],
'6': ['g'],
'7': ['t'],
'8': ['b'],
'9': ['g', 'o'],
'0': ['o'],
'@': ['a'],
'(': ['c'],
'[': ['c'],
'<': ['c'],
'&': ['g'],
'!': ['i'],
'|': ['i', 'l'],
'?': ['n', 'r'],
'$': ['s'],
'+': ['t'],
'%': ['x']
},
regex: {
repetition: {
single: /(.)\1+/g,
group: /(..+)\1+/g
},
date: {
DMY: /(0?[1-9]|[12][0-9]|3[01])([\- \/.])?(0?[1-9]|1[012])([\- \/.])?([0-9]{2})/g,
DMYY: /(0?[1-9]|[12][0-9]|3[01])([\- \/.])?(0?[1-9]|1[012])([\- \/.])?([0-9]{4})/g,
MDY: /(0?[1-9]|1[012])([\- \/.])?(0?[1-9]|[12][0-9]|3[01])([\- \/.])?([0-9]{2})/g,
MDYY: /(0?[1-9]|1[012])([\- \/.])?(0?[1-9]|[12][0-9]|3[01])([\- \/.])?([0-9]{4})/g,
YDM: /([0-9]{2})([\- \/.])?(0?[1-9]|[12][0-9]|3[01])([\- \/.])?(0?[1-9]|1[012])/g,
YYDM: /([0-9]{4})([\- \/.])?(0?[1-9]|[12][0-9]|3[01])([\- \/.])?(0?[1-9]|1[012])/g,
YMD: /([0-9]{2})([\- \/.])?(0?[1-9]|1[012])([\- \/.])?(([0-9]{2})?[0-9]{2})(0?[1-9]|[12][0-9]|3[01])/g,
YYMD: /([0-9]{4})([\- \/.])?(0?[1-9]|1[012])([\- \/.])?(([0-9]{2})?[0-9]{2})(0?[1-9]|[12][0-9]|3[01])/g,
DM: /(0?[1-9]|[12][0-9]|3[01])([\- \/.])?(0?[1-9]|1[012])/g,
MY: /(0?[1-9]|1[012])([\- \/.])?([0-9]{2})/g,
MYY: /(0?[1-9]|1[012])([\- \/.])?([0-9]{4})/g
},
number: /[0-9]+/,
numberOnly: /^[0-9]+$/,
punctuation: /[\^°!"§\$%&\/\(\)=\?\\\.:,;\-_#'\+~\*<>\|\[\]\{\}`´]+/, // 34
punctuationOnly: /^[\^°!"§\$%&\/\(\)=\?\\\.:,;\-_#'\+~\*<>\|\[\]\{\}`´]+$/,
lower: /[a-z]+/,
lowerOnly: /^[a-z]+$/,
upper: /[A-Z]+/,
upperOnly: /^[A-Z]+$/,
upperFirst: /^[A-Z]+[A-Za_z]*$/,
upperFirstOnly: /^[A-Z]{1}[a-z]+$/
},
keyboards: {
QWERTZ: new Keyboard({ // 480/105 = 4.571428571
'^': [null, null, null, '^°', '1!', null, null], // 1
'°': [null, null, null, '^°', '1!', null, null], // 1
'1': [null, null, '^°', '1!', '2"', null, 'qQ@'], // 3
'!': [null, null, '^°', '1!', '2"', null, 'qQ@'], // 3
'2': [null, null, '1!', '2"', '3§', 'qQ@', 'wW'], // 4
'"': [null, null, '1!', '2"', '3§', 'qQ@', 'wW'], //4
'3': [null, null, '2"', '3§', '4$', 'wW', 'eE€'], //4
'§': [null, null, '2"', '3§', '4$', 'wW', 'eE€'], //4
'4': [null, null, '3§', '4$', '5%', 'eE€', 'rR'], //4
'$': [null, null, '3§', '4$', '5%', 'eE€', 'rR'], //4
'5': [null, null, '4$', '5%', '6&', 'rR', 'tT'], //4
'%': [null, null, '4$', '5%', '6&', 'rR', 'tT'], //4
'6': [null, null, '5%', '6&', '7/{', 'tT', 'yY'], //4
'&': [null, null, '5%', '6&', '7/{', 'tT', 'yY'], //4
'7': [null, null, '6&', '7/{', '8([', 'yY', 'uU'], //4
'/': [null, null, '6&', '7/{', '8([', 'yY', 'uU'], //4
'{': [null, null, '6&', '7/{', '8([', 'yY', 'uU'], //4
'8': [null, null, '7/{', '8([', '9)]', 'uU', 'iI'], // 4
'(': [null, null, '7/{', '8([', '9)]', 'uU', 'iI'], // 4
'[': [null, null, '7/{', '8([', '9)]', 'uU', 'iI'], // 4
'9': [null, null, '8([', '9)]', '0=}', 'iI', 'oO'], // 4
')': [null, null, '8([', '9)]', '0=}', 'iI', 'oO'], // 4
']': [null, null, '8([', '9)]', '0=}', 'iI', 'oO'], // 4
'0': [null, null, '9)]', '0=}', 'ß?\\', 'oO', 'pP'], // 4
'=': [null, null, '9)]', '0=}', 'ß?\\', 'oO', 'pP'], // 4
'}': [null, null, '9)]', '0=}', 'ß?\\', 'oO', 'pP'], // 4
'ß': [null, null, '0=}', 'ß?\\', '´`', 'pP', 'üÜ'], // 4
'?': [null, null, '0=}', 'ß?\\', '´`', 'pP', 'üÜ'], // 4
'\\': [null, null, '0=}', 'ß?\\', '´`', 'pP', 'üÜ'], // 4
'`': [null, null, 'ß?\\', '´`', null, 'üÜ', '+*~'], // 3
'´': [null, null, 'ß?\\', '´`', null, 'üÜ', '+*~'], // 3
'q': ['1!', '2"', null, 'qQ@', 'wW', null, 'aA'], // 4
'Q': ['1!', '2"', null, 'qQ@', 'wW', null, 'aA'], // 4
'@': ['1!', '2"', null, 'qQ@', 'wW', null, 'aA'], // 4
'w': ['2"', '3§', 'qQ@', 'wW', 'eE€', 'aA', 'sS'], // 6
'W': ['2"', '3§', 'qQ@', 'wW', 'eE€', 'aA', 'sS'], // 6
'e': ['3§', '4$', 'wW', 'eE€', 'rR', 'sS', 'dD'], // 6
'E': ['3§', '4$', 'wW', 'eE€', 'rR', 'sS', 'dD'], // 6
'€': ['3§', '4$', 'wW', 'eE€', 'rR', 'sS', 'dD'], // 6
'r': ['4$', '5%', 'eE€', 'rR', 'tT', 'dD', 'fF'], // 6
'R': ['4$', '5%', 'eE€', 'rR', 'tT', 'dD', 'fF'], // 6
't': ['5%', '6&', 'rR', 'tT', 'zZ', 'fF', 'gG'], // 6
'T': ['5%', '6&', 'rR', 'tT', 'zZ', 'fF', 'gG'], // 6
'z': ['6&', '7/{', 'tT', 'zZ', 'uU', 'gG', 'hH'], // 6
'Z': ['6&', '7/{', 'tT', 'zZ', 'uU', 'gG', 'hH'], // 6
'u': ['7/{', '8([', 'zZ', 'uU', 'iI', 'hH', 'jJ'], // 6
'U': ['7/{', '8([', 'zZ', 'uU', 'iI', 'hH', 'jJ'], // 6
'i': ['8([', '9)]', 'uU', 'iI', 'oO', 'jJ', 'kK'], // 6
'I': ['8([', '9)]', 'uU', 'iI', 'oO', 'jJ', 'kK'], // 6
'o': ['9)]', '0=}', 'iI', 'oO', 'pP', 'kK', 'lL'], // 6
'O': ['9)]', '0=}', 'iI', 'oO', 'pP', 'kK', 'lL'], // 6
'p': ['0=}', 'ß?\\', 'oO', 'pP', 'üÜ', 'lL', 'öÖ'], // 6
'P': ['0=}', 'ß?\\', 'oO', 'pP', 'üÜ', 'lL', 'öÖ'], // 6
'ü': ['ß?\\', '´`', 'pP', 'üÜ', '+*~', 'öÖ', 'äÄ'], // 6
'Ü': ['ß?\\', '´`', 'pP', 'üÜ', '+*~', 'öÖ', 'äÄ'], // 6
'+': ['´``', null, 'üÜ', '+*~', null, 'äÄ', '\'#'], // 4
'*': ['´``', null, 'üÜ', '+*~', null, 'äÄ', '\'#'], // 4
'~': ['´``', null, 'üÜ', '+*~', null, 'äÄ', '\'#'], // 4
'a': ['qQ@', 'wW', null, 'aA', 'sS', '<>|', 'yY'], // 5
'A': ['qQ@', 'wW', null, 'aA', 'sS', '<>|', 'yY'], // 5
's': ['wW', 'eE€', 'aA', 'sS', 'dD', 'yY', 'xX'], // 6
'S': ['wW', 'eE€', 'aA', 'sS', 'dD', 'yY', 'xX'], // 6
'd': ['eE€', 'rR', 'sS', 'dD', 'fF', 'xX', 'cC'], // 6
'D': ['eE€', 'rR', 'sS', 'dD', 'fF', 'xX', 'cC'], // 6
'f': ['rR', 'tT', 'dD', 'fF', 'gG', 'cC', 'vV'], // 6
'F': ['rR', 'tT', 'dD', 'fF', 'gG', 'cC', 'vV'], // 6
'g': ['tT', 'zZ', 'fF', 'gG', 'hH', 'vV', 'bB'], // 6
'G': ['tT', 'zZ', 'fF', 'gG', 'hH', 'vV', 'bB'], // 6
'h': ['zZ', 'uU', 'gG', 'hH', 'jJ', 'bB', 'nN'], // 6
'H': ['zZ', 'uU', 'gG', 'hH', 'jJ', 'bB', 'nN'], // 6
'j': ['uU', 'iI', 'hH', 'jJ', 'kK', 'nN', 'mM'], // 6
'J': ['uU', 'iI', 'hH', 'jJ', 'kK', 'nN', 'mM'], // 6
'k': ['iI', 'oO', 'jJ', 'kK', 'lL', 'mM', ',;'], // 6
'K': ['iI', 'oO', 'jJ', 'kK', 'lL', 'mM', ',;'], // 6
'l': ['oO', 'pP', 'kK', 'lL', 'öÖ', ',;', '.:'], // 6
'L': ['oO', 'pP', 'kK', 'lL', 'öÖ', ',;', '.:'], // 6
'ö': ['pP', 'üÜ', 'lL', 'öÖ', 'äÄ', '.:', '-_'], // 6
'Ö': ['pP', 'üÜ', 'lL', 'öÖ', 'äÄ', '.:', '-_'], // 6
'ä': ['üÜ', '+*~', 'öÖ', 'äÄ', '#\'', '-_', null], // 5
'Ä': ['üÜ', '+*~', 'öÖ', 'äÄ', '#\'', '-_', null], // 5
'#': ['+*~', null, 'äÄ', '#\'', null, null, null], // 2
'\'': ['+*~', null, 'äÄ', '#\'', null, null, null], // 2
'<': [null, 'aA', null, '<>|', 'yY', null, null], // 2
'>': [null, 'aA', null, '<>|', 'yY', null, null], // 2
'|': [null, 'aA', null, '<>|', 'yY', null, null], // 2
'y': ['aA', 'sS', '<>|', 'yY', 'xX', null, null], // 4
'Y': ['aA', 'sS', '<>|', 'yY', 'xX', null, null], // 4
'x': ['sS', 'dD', 'yY', 'xX', 'cC', null, null], // 4
'X': ['sS', 'dD', 'yY', 'xX', 'cC', null, null], // 4
'c': ['dD', 'fF', 'xX', 'cC', 'vV', null, null], // 4
'C': ['dD', 'fF', 'xX', 'cC', 'vV', null, null], // 4
'v': ['fF', 'gG', 'cC', 'vV', 'bB', null, null], // 4
'V': ['fF', 'gG', 'cC', 'vV', 'bB', null, null], // 4
'b': ['gG', 'hH', 'vV', 'bB', 'nN', null, null], // 4
'B': ['gG', 'hH', 'vV', 'bB', 'nN', null, null], // 4
'n': ['hH', 'jJ', 'bB', 'nN', 'mM', null, null], // 4
'N': ['hH', 'jJ', 'bB', 'nN', 'mM', null, null], // 4
'm': ['jJ', 'kK', 'nN', 'mM', ',;', null, null], // 4
'M': ['jJ', 'kK', 'nN', 'mM', ',;', null, null], // 4
',': ['kK', 'lL', 'mM', ',;', '.:', null, null], // 4
';': ['kK', 'lL', 'mM', ',;', '.:', null, null], // 4
'.': ['lL', 'öÖ', ',;', '.:', '-_', null, null], // 4
':': ['lL', 'öÖ', ',;', '.:', '-_', null, null], // 4
'-': ['öÖ', 'ä', '.:', '-_', null, null, null], // 3
'_': ['öÖ', 'ä', '.:', '-_', null, null, null] // 3
}, 4.571428571),
QWERTY: new Keyboard({
'~': [null, null, null, '~`', '1!', null, null], // 1
'`': [null, null, null, '~`', '1!', null, null], // 1
'1': [null, null, '`~', '1!', '2@', null, 'qQ'], // 3
'!': [null, null, '`~', '1!', '2@', null, 'qQ'], // 3
'2': [null, null, '1!', '2@', '3#', 'qQ', 'wW'], // 4
'@': [null, null, '1!', '2@', '3#', 'qQ', 'wW'], //4
'3': [null, null, '2@', '3#', '4$', 'wW', 'eE'], //4
'#': [null, null, '2@', '3#', '4$', 'wW', 'eE'], //4
'4': [null, null, '3#', '4$', '5%', 'eE', 'rR'], //4
'$': [null, null, '3#', '4$', '5%', 'eE', 'rR'], //4
'5': [null, null, '4$', '5%', '6^', 'rR', 'tT'], //4
'%': [null, null, '4$', '5%', '6^', 'rR', 'tT'], //4
'6': [null, null, '5%', '6^', '7&', 'tT', 'yY'], //4
'^': [null, null, '5%', '6^', '7&', 'tT', 'yY'], //4
'7': [null, null, '6^', '7&', '8*', 'yY', 'uU'], //4
'&': [null, null, '6^', '7&', '8*', 'yY', 'uU'], //4
'8': [null, null, '7&', '8*', '9(', 'uU', 'iI'], // 4
'*': [null, null, '7&', '8*', '9(', 'uU', 'iI'], // 4
'9': [null, null, '8*', '9(', '0)', 'iI', 'oO'], // 4
'(': [null, null, '8*', '9(', '0)', 'iI', 'oO'], // 4
'0': [null, null, '9(', '0)', '-_', 'oO', 'pP'], // 4
')': [null, null, '9(', '0)', '-_', 'oO', 'pP'], // 4
'-': [null, null, '0)', '-_', '=+', 'pP', '[{'], // 4
'_': [null, null, '0)', '-_', '=+', 'pP', '[{'], // 4
'=': [null, null, '-_', '=+', '\\|', '{[', '}]'], // 4
'+': [null, null, '-_', '=+', '\\|', '{[', '}]'], // 4
'\\': [null, null, '=+', '\\|', null, '}]', null], // 2
'|': [null, null, '=+', '\\|', null, '}]', null], // 2
'q': ['1!', '2@', null, 'qQ', 'wW', null, 'aA'], // 4
'Q': ['1!', '2@', null, 'qQ', 'wW', null, 'aA'], // 4
'w': ['2@', '3#', 'qQ', 'wW','eE', 'aA', 'sS'], // 6
'W': ['2@', '3#', 'qQ', 'wW','eE', 'aA', 'sS'], // 6
'e': ['3#', '4$', 'wW', 'eE', 'rR', 'sS', 'dD'], // 6
'E': ['3#', '4$', 'wW', 'eE', 'rR', 'sS', 'dD'], // 6
'r': ['4$', '5%', 'eE', 'rR', 'tT', 'dD', 'fF'], // 6
'R': ['4$', '5%', 'eE', 'rR', 'tT', 'dD', 'fF'], // 6
't': ['5%', '6^', 'rR', 'tT', 'yY', 'fF', 'gG'], // 6
'T': ['5%', '6^', 'rR', 'tT', 'yY', 'fF', 'gG'], // 6
'y': ['6^', '7&', 'tT', 'yY', 'uU', 'gG', 'hH'], // 6
'Y': ['6^', '7&', 'tT', 'yY', 'uU', 'gG', 'hH'], // 6
'u': ['7&', '8*', 'yY', 'uU', 'iI', 'hH', 'jJ'], // 6
'U': ['7&', '8*', 'yY', 'uU', 'iI', 'hH', 'jJ'], // 6
'i': ['8*', '9(', 'uU', 'iI', 'oO', 'jJ', 'kK'], // 6
'I': ['8*', '9(', 'uU', 'iI', 'oO', 'jJ', 'kK'], // 6
'o': ['9(', '0)', 'iI', 'oO', 'pP', 'kK', 'lL'], // 6
'O': ['9(', '0)', 'iI', 'oO', 'pP', 'kK', 'lL'], // 6
'p': ['0)', '-_', 'oO', 'pP', '[{', 'lL', ':;'], // 6
'P': ['0)', '-_', 'oO', 'pP', '[{', 'lL', ':;'], // 6
'[': ['-_', '=+', 'pP', '[{', ']}', ':;', '\'"'], // 6
'{': ['-_', '=+', 'pP', '[{', ']}', ':;', '\'"'], // 6
']': ['=+', '\\|', '[{', ']}', null, '\'"', null], // 4
'}': ['=+', '\\|', '[{', ']}', null, '\'"', null], // 4
'a': ['qQ', 'wW', null, 'aA', 'sS', null, 'zZ'], // 4
'A': ['qQ', 'wW', null, 'aA', 'sS', null, 'zZ'], // 4
's': ['wW', 'eE', 'aA', 'sS', 'dD', 'zZ', 'xX'], // 6
'S': ['wW', 'eE', 'aA', 'sS', 'dD', 'zZ', 'xX'], // 6
'd': ['eE', 'rR', 'sS', 'dD', 'fF', 'xX', 'cC'], // 6
'D': ['eE', 'rR', 'sS', 'dD', 'fF', 'xX', 'cC'], // 6
'f': ['rR', 'tT', 'dD', 'fF', 'gG', 'cC', 'vV'], // 6
'F': ['rR', 'tT', 'dD', 'fF', 'gG', 'cC', 'vV'], // 6
'g': ['tT', 'yY', 'fF', 'gG', 'hH', 'vV', 'bB'], // 6
'G': ['tT', 'yY', 'fF', 'gG', 'hH', 'vV', 'bB'], // 6
'h': ['yY', 'uU', 'gG', 'hH', 'jJ', 'bB', 'nN'], // 6
'H': ['yY', 'uU', 'gG', 'hH', 'jJ', 'bB', 'nN'], // 6
'j': ['uU', 'iI', 'hH', 'jJ', 'kK', 'nN', 'mM'], // 6
'J': ['uU', 'iI', 'hH', 'jJ', 'kK', 'nN', 'mM'], // 6
'k': ['iI', 'oO', 'jJ', 'kK', 'lL', 'mM', ',;'], // 6
'K': ['iI', 'oO', 'jJ', 'kK', 'lL', 'mM', ',;'], // 6
'l': ['oO', 'pP', 'kK', 'lL', ':;', ',<', '.>'], // 6
'L': ['oO', 'pP', 'kK', 'lL', ':;', ',<', '.>'], // 6
':': ['pP', '[{', 'lL', ':;', '\'"', '.>', '?/'], // 6
';': ['pP', '[{', 'lL', ':;', '\'"', '.>', '?/'], // 6
'\'': ['[{', ']}', ':;', '\'"', null, '?/', null], // 5
'"': ['[{', ']}', ':;', '\'"', null, '?/', null], // 5
'z': ['aA', 'sS', null, 'zZ', 'xX', null, null], // 4
'Z': ['aA', 'sS', null, 'zZ', 'xX', null, null], // 4
'x': ['sS', 'dD', 'zZ', 'xX', 'cC', null, null], // 4
'X': ['sS', 'dD', 'zZ', 'xX', 'cC', null, null], // 4
'c': ['dD', 'fF', 'xX', 'cC', 'vV', null, null], // 4
'C': ['dD', 'fF', 'xX', 'cC', 'vV', null, null], // 4
'v': ['fF', 'gG', 'cC', 'vV', 'bB', null, null], // 4
'V': ['fF', 'gG', 'cC', 'vV', 'bB', null, null], // 4
'b': ['gG', 'hH', 'vV', 'bB', 'nN', null, null], // 4
'B': ['gG', 'hH', 'vV', 'bB', 'nN', null, null], // 4
'n': ['hH', 'jJ', 'bB', 'nN', 'mM', null, null], // 4
'N': ['hH', 'jJ', 'bB', 'nN', 'mM', null, null], // 4
'm': ['jJ', 'kK', 'nN', 'mM', ',<', null, null], // 4
'M': ['jJ', 'kK', 'nN', 'mM', ',<', null, null], // 4
',': ['kK', 'lL', 'mM', ',<', '.>', null, null], // 4
'<': ['kK', 'lL', 'mM', ',<', '.>', null, null], // 4
'.': ['lL', ':;', ',<', '.>', '/?', null, null], // 4
'>': ['lL', ':;', ',<', '.>', '/?', null, null], // 4
'/': [':;', '\'"', '.>', '/?', null, null, null], // 3
'?': [':;', '\'"', '.>', '/?', null, null, null] // 3
}, 4.571428571),
QWERTZNumpad: new Keyboard({
'0': ['1', '2', ',', null], // 2
',': ['3', '0', null, null], // 2
'1': ['4', null, '2', '0'], // 3
'2': ['5', '1', '3', '0'], // 4
'3': ['3', '2', null, ','], // 3
'4': ['7', null, '5', '1'], // 3
'5': ['8', '4', '6', '2'], // 4
'6': ['9', '5', '6', '2'], // 4
'7': [null, null, '8', '4'], // 2
'8': ['/', '7', '9', '5'], // 4
'9': ['*', '8', '+', '6'], // 4
'+': ['-', '9', '6', null], // 3
'/': [null, null, '*', '8'], // 2
'*': [null, '/', '-', '9'], // 3
'-': [null, '*', null, '+'] // 2
}, 3),
QWERTYNumpad: new Keyboard({
'0': ['1', '2', ',', null], // 2
',': ['3', '0', null, null], // 2
'1': ['4', null, '2', '0'], // 3
'2': ['5', '1', '3', '0'], // 4
'3': ['6', '2', null, ','], // 3
'4': ['7', null, '5', '1'], // 3
'5': ['8', '4', '6', '2'], // 4
'6': ['9', '5', '+', '2'], // 4
'7': [null, null, '8', '4'], // 2
'8': ['/', '7', '9', '5'], // 4
'9': ['*', '8', '+', '6'], // 4
'+': ['-', '9', '6', null], // 3
'/': [null, null, '*', '8'], // 2
'*': [null, '/', '-', '9'], // 3
'-': [null, '*', null, '+'] // 2
}, 3)
},
/**
* Cache will hold all collected matches of the last score estimation.
*
* @property cache
* @type {object}
*/
cache: {
/**
* Clear the cache.
*/
clear: function() {
for (var key in this) {
if (key !== 'set' && key !== 'clear') {
this[key] = undefined;
}
}
},
/**
* Set cache data.
*
* @param {string} key
* @param {mixed} value
*/
set: function(key, value) {
this[key] = value;
}
},
/**
* Represent log of abse 2.
*
* @param {number} x
* @return {number}
*/
lg: function(x) {
return Math.log(x)/Math.log(2);
},
/**
* Get the time to crack.
*
* @param {number} entropy
* @param {number} cores
* @return {number}
*/
calculateAverageTimeToCrack: function(entropy, cores) {
return 0.5*Math.pow(2, entropy)*0.005/cores;
},
/**
* Calculates a naive score ased on the brute force entropy.
*
* @param {string} password
* @return {number}
*/
calculateBruteForceEntropy: function() {
var base = 0;
if (this.regex['lower'].test(this.password)) {
base += this.LOWER;
}
if (this.regex['upper'].test(this.password)) {
base += this.UPPER;
}
if (this.regex['number'].test(this.password)) {
base += this.NUMBER;
}
if (this.regex['punctuation'].test(this.password)) {
base += this.PUNCTUATION;
}
var naiveEntropy = this.lg(base)*this.password.length;
this.cache.set('naiveEntropy', naiveEntropy);
return naiveEntropy;
},
/**
* Gather matches using the given sources.
*
* @param {array} options
* @param {boolean} append
* @returns {number}
*/
collectMatches: function(options, append) {
// Default parameters: use default options or append to default options.
options = options || [];
append = append || true;
var defaultOptions = this.options || [];
if (append === true) {
options = options.concat(defaultOptions);
}
var matches = [];
// First collect all possible matches.
for (var i = 0; i < options.length; i++) {
var optionMatches = [];
if (!'type' in options[i]) {
continue;
}
switch (options[i]['type']) {
// Dictionary used for word lists, passwords, names, cities etc.
case 'dictionary':
if ('dictionary' in options[i]) {
optionMatches = this.collectDictionaryMatches(options[i]['dictionary']);
if ('leet' in options[i] && options[i]['leet'] === true) {
var leetMatches = this.collectLeetSpeakMatches(options[i]['dictionary']);
optionMatches = optionMatches.concat(leetMatches);
}
}
break;
case 'keyboard':
if ('keyboard' in options[i]) {
optionMatches = this.collectKeyboardMatches(options[i]['keyboard']);
}
break;
case 'repetition':
optionMatches = this.collectRepetitionMatches();
break;
case 'sequences':
optionMatches = this.collectSequenceMatches();
break;
case 'dates':
optionMatches = this.collectDateMatches();
break;
}
if ('key' in options[i]) {
this.cache.set(options[i]['key'], optionMatches);
}
matches = matches.concat(optionMatches);
}
return matches;
},
/**
* Calculate entropy score.
*
* @param {array} options
* @param {boolean} append
* @return {number}
*/
calculateEntropyScore: function(options, append) {
var matches = this.collectMatches(options, append);
var entropies = [];
var entropyMatches = [];
var currentEntropy = this.calculateBruteForceEntropy(this.password);
// Minimize entropy as far as possible. This approach assumes the attacker
// to know as much as possible about the form of the password.
for (var i = 0; i < this.password.length; i++) {
// Add current character as match.
// If the character is not found within a pattern this will be taken.
matches[matches.length] = {
pattern: this.password[i],
entropy: this.calculateBruteForceEntropy(this.password[i]),
start: i,
end: i,
type: 'letter'
};
// Set to infinity - we want to minimize the entropy.
entropies[i] = Number.POSITIVE_INFINITY;
for (var j = 0; j < matches.length; j++) {
var start = matches[j]['start'];
var end = matches[j]['end'];
if (end !== i) {
continue;
}
var currentEntropy = matches[j]['entropy'];
if (start > 0) {
currentEntropy += entropies[start - 1];
}
if (currentEntropy < entropies[i]) {
entropies[i] = currentEntropy;
entropyMatches[i] = matches[j];
}
}
}
// Gather the used matches.
var minimumMatches = [];
var i = this.password.length - 1;
while (i >= 0) {
if (entropyMatches[i]) {
minimumMatches[minimumMatches.length] = entropyMatches[i];
i = entropyMatches[i]['start'] - 1;
}
else {
i--;
}
}
this.cache.set('minimumMatches', minimumMatches);
this.cache.set('entropy', entropies[this.password.length - 1]);
return entropies[this.password.length - 1];
},
/**
* Check whether string ocurres in the dictionary.
*
* @param {array} dictionary
* @return {array}
*/
collectDictionaryMatches: function(dictionary) {
var matches = [];
for (var i = 0; i < this.password.length; i++) {
for (var j = i; j < this.password.length; j++) {
var original = this.password.substring(i, j + 1);
var string = original.toLowerCase();
var reversed = this.getReversedString(string);
// Simple match.
if (string in dictionary) {
if (dictionary[string]) {
matches[matches.length] = {
pattern: original,
entropy: this.calculateDictionaryEntropy(original, string, dictionary[string]),
start: i,
end: j,
type: 'dictionary'
};
}
}
// Reversed match.
if (reversed in dictionary) {
if (dictionary[reversed]) {
matches[matches.length] = {
pattern: original,
entropy: this.calculateReversedDictionaryEntropy(original, string, dictionary[string]),
start: i,
end: j,
type: 'dictionary'
};
}
}
}
}
return matches;
},
/**
* Calculate entropy for dictionary.
*
* @param {string} original
* @param {string} word
* @param {number} rank
* @return {number}
*/
calculateDictionaryEntropy: function(original, word, rank) {
if (this.regex['lower'].test(original) && this.regex['upper'].test(original)) {
// First upper only is simple capitalization.
if (this.regex['upperFirstOnly'].test(original)) {
return this.lg(rank) + 1;
}
else {
// Base entropy plus entropy of possiblities to choose between upper and lower per letter.
return this.lg(rank) + original.length; // = lg(rank) + lg(2^original.length) = lg(rank) + lg(2)*original.length
}
}
return this.lg(rank);
},
/**
* Calculate dictionary entropy for reversed.
*
* @param {string} original
* @param {string} word
* @param {number} rank
* @return {number}
*/
calculateReversedDictionaryEntropy: function(original, word, rank) {
// Two possibilities: reversed or not => 1 bit of extra entropy.
return this.calculateDictionaryEntropy(original, word, rank) + 1;
},
/**
* Search all leet speak matches.
*
* @param {array} dictionary
* @return {array}
*/
collectLeetSpeakMatches: function(dictionary) {
var matches = [];
var subs = this.collectLeetSpeakSubstitutions(this.password);
for (var k = 0; k < subs.length; k++) {
for (var i = 0; i < subs[k].length; i++) {
for (var j = i; j < subs[k].length; j++) {
var original = subs[k].substring(i, j + 1);
var string = original.toLowerCase();
var reversed = this.getReversedString(string);
var originalPattern = this.password.substring(i, j + 1);
if (string in dictionary) {
if (dictionary[string]) {
matches[matches.length] = {
pattern: originalPattern,
entropy: this.calculateLeetSpeakEntropy(this.password.substring(i, j + 1), string, dictionary[string]),
start: i,
end: j,
type: 'leet'
};
}
}
if (reversed in dictionary) {
if (dictionary[string]) {
matches[matches.length] = {
pattern: originalPattern,
entropy: this.calculateReversedLeetSpeakEntropy(this.password.substring(i, j + 1), string, dictionary[string]),
start: i,
end: j,
type: 'leet'
};
}
}
}
}
}
return matches;
},
/**
* Calculate dictionary entropy for leet speak.
*
* @param {string} original
* @param {string} word
* @param {number} rank
* @return {number}
*/
calculateLeetSpeakEntropy: function(original, word, rank) {
// Simple apporach: calculate possiblities of leet speak substitutions.
var possibilities = 1;
for (var key in this.leet) {
if (original.indexOf(key) >= 0) {
// Add the possiblity to not substitute.
possibilities *= (this.leet[key].length + 1);
}
}
return this.calculateDictionaryEntropy(original, word, rank) + this.lg(possibilities);
},
/**
* Calculate leet speak entropy for reversed.
*
* @param {string} original
* @param {string} word
* @param {number} rank
* @return {number}
*/
calculateReversedLeetSpeakEntropy: function(original, word, rank) {
// Two possibilities: reversed or not => 1 bit of extra entropy.
return this.calculateLeetSpeakEntropy(original, word, rank) + 1;
},
/**
* Get all leet speak substitutions.
*
* Considering a leet speak substitution matrix with a row for each letter to be translated
* we iterate over all columns giving one
*
* @return {array}
*/
collectLeetSpeakSubstitutions: function() {
var subs = [];
var leet = {};
for (var char in this.leet) {
if (this.password.indexOf(char) >= 0) {
leet[char] = this.leet[char];
}
}
var recursiveSubstitutions = function(string) {
if (string[0] in leet) {
if (string.length === 1) {
return leet[string[0]];
}
else {
var substrings = recursiveSubstitutions(string.substring(1, string.length));
var subs = [];
for (var i = 0; i < substrings.length; i++) {
for (var j = 0; j < leet[string[0]].length; j++) {
subs[subs.length] = leet[string[0]][j] + substrings[i];
}
}
return subs;
}
}
else {
if (string.length === 1) {
return [string[0]];
}
else {
var substrings = recursiveSubstitutions(string.substring(1, string.length));
var subs = [];
for (var i = 0; i < substrings.length; i++) {
subs[subs.length] = string[0] + substrings[i];
}
return subs;
}
}
};
return recursiveSubstitutions(this.password);
},
/**
* Get all matched paths on the given keyboard.
*
* @param {object} keyboard
* @return {array}
*/
collectKeyboardMatches: function(keyboard) {
var matches = [];
var currentPath = this.password[0];
var currentTurns = 0;
var currentStart = 0;
// Keyboard automatically takes care of lower and upper case and special characters.
for (var i = 0; i < this.password.length - 1; i++) {
if (keyboard.areAdjacent(this.password[i], this.password[i + 1])) {
currentPath += this.password[i + 1];
if (this.password[i + 1] !== this.password[i]) {
currentTurns++;
}
}
else {
matches[matches.length] = {
pattern: currentPath,
entropy: this.calculateKeyboardEntropy(currentPath, currentTurns, keyboard),
start: currentStart,
end: i,
type: 'keyboard'
};
currentPath = this.password[i + 1];
currentTurns = 0;
currentStart = i + 1;
}
}
// Remember to add the last path.
if (currentPath.length > 0) {
matches[matches.length] = {
pattern: currentPath,
entropy: this.calculateKeyboardEntropy(currentPath, currentTurns, keyboard),
start: currentStart,
end: this.password.length - 1,
type: 'keyboard'
};
}
return matches;
},
/**
* Calculate entropy for keyboard patterns.
*
* @param {string} original
* @param {number} turns
* @param {object} keyboard
* @return {number}
*/
calculateKeyboardEntropy: function(original, turns, keyboard) {
// Initialization with 0 will not work because lg(0) is undefined.
var possiblities = 1;
if (this.regex['lower'].test(original[0])) {
possiblities = this.LOWER;
}
else if (this.regex['upper'].test(original[0])) {
possiblities = this.UPPER;
}
else if (this.regex['number'].test(original[0])) {
possiblities = this.NUMBER;
}
else if (this.regex['punctuation'].test(original[0])) {
possiblities = this.PUNCTUATION;
}
return this.lg(possiblities) + turns*this.lg(keyboard.averageNeighbours);
},
/**
* Check for all repetitions.
*
* @return {array}
*/
collectRepetitionMatches: function() {
var matches = [];
var singleMatches = this.password.match(this.regex['repetition']['single']) || [];
for (var i = 0; i < singleMatches.length; i++) {
matches[matches.length] = {
pattern: singleMatches[i],
entropy: this.calculateSingleRepetitionEntropy(singleMatches[i]),
start: this.password.indexOf(singleMatches[i]),
end: this.password.indexOf(singleMatches[i]) + singleMatches[i].length - 1,
type: 'repetition'
};
}
var groupMatches = this.password.match(this.regex['repetition']['group']) || [];
for (var i = 0; i < groupMatches.length; i++) {
matches[matches.length] = {
pattern: groupMatches[i],
entropy: this.calculateGroupRepetitionEntropy(groupMatches[i]),
start: this.password.indexOf(groupMatches[i]),
end: this.password.indexOf(groupMatches[i]) + groupMatches[i].length - 1,
type: 'repetition'
};
}
return matches;
},
/**
* Calculate repetition entropy.
*
* @param {string} original substring
* @return {number}
*/
calculateSingleRepetitionEntropy: function(original) {
if (this.regex['number'].test(original)) {
return this.lg(this.NUMBER*original.length);
}
if (this.regex['lower'].test(original) || this.regex['upper'].test(original)) {
return this.lg(this.LOWER*original.length);
}
if (this.regex['punctuation'].test(original)) {
return this.lg(this.PUNCTUATION*original.length);
}
return this.calculateBruteForceEntropy(original);
},
/**
* Calculate repetition entropy for groups.
*
* @param {string} original substring
* @return {number}
*/
calculateGroupRepetitionEntropy: function(original) {
// First determine the length of the repeated string.
var result = this.regex['repetition']['group'].exec(original);
var length = original.length;
while (result !== null) {
length = result[1].length;
result = this.regex['repetition']['group'].exec(result[1]);
}
var possibilities = 0;
if (this.regex['number'].test(original)) {
possibilities += this.NUMBER;
}
if (this.regex['lower'].test(original) || this.regex['upper'].test(original)) {
possibilities += this.LOWER;
}
if (this.regex['punctuation'].test(original)) {
possibilities += this.PUNCTUATION;
}
return this.lg(possibilities*length);
},
/**
* Check for sequences.
*
* @return {array}
*/
collectSequenceMatches: function() {
var lowerSeq = '';
var lowerRevSeq = '';
var numberSeq = '';
var numberRevSeq = '';
for (var i = 0; i < this.password.length; i++) {
// At least two characters needed for a sequence.
for (var j = i + 2; j <= this.password.length; j++) {
var original = this.password.substring(i, j);
var string = original.toLowerCase();
var reversed = this.getReversedString(string);
if (string.length === 0) {
continue;
}
// Check alphabetical sequence.
if (this.sequences['lower'].indexOf(string) >= 0 && string.length > lowerSeq.length) {
lowerSeq = original;
}
if (this.sequences['lower'].indexOf(reversed) >= 0 && string.length > lowerRevSeq.length) {
lowerRevSeq = original;
}
// Check number sequence.
if (this.sequences['numbers'].indexOf(string) >= 0 && string.length > numberSeq.length) {
numberSeq = original;
}
if (this.sequences['numbers'].indexOf(reversed) >= 0 && string.length > numberSeq.length) {
numberRevSeq = original;
}
}
}
var matches = [];
if (lowerSeq.length > 0) {
matches[matches.length] = {
pattern: lowerSeq,
entropy: this.calculateSequenceEntropy(lowerSeq),
start: this.password.indexOf(lowerSeq),
end: this.password.indexOf(lowerSeq) + lowerSeq.length - 1,
type: 'sequence'
};
}
if (lowerRevSeq.length > 0) {
matches[matches.length] = {
pattern: lowerRevSeq,
entropy: this.calculateSequenceEntropy(lowerRevSeq),
start: this.password.indexOf(lowerRevSeq),
end: this.password.indexOf(lowerRevSeq) + lowerRevSeq.length - 1,
type: 'sequence'
};
}
if (numberSeq.length > 0) {
matches[matches.length] = {
pattern: numberSeq,
entropy: this.calculateSequenceEntropy(numberSeq),
start: this.password.indexOf(numberSeq),
end: this.password.indexOf(numberSeq) + numberSeq.length - 1,
type: 'sequence'
};
}
if (numberRevSeq.length > 0) {
matches[matches.length] = {
pattern: numberRevSeq,
entropy: this.calculateSequenceEntropy(numberRevSeq),
start: this.password.indexOf(numberRevSeq),
end: this.password.indexOf(numberRevSeq) + numberRevSeq.length - 1,
type: 'sequence'
};
}
return matches;
},
/**
* Calculate entropy for sequence.
*
* @param {string} original substring
* @return {number}
*/
calculateSequenceEntropy: function(original) {
if (this.regex['number'].test(original)) {
return this.lg(original.length*this.NUMBER);
}
else if (this.regex['lowerOnly'].test(original)) {
return this.lg(original.length*this.LOWER);
}
else if (this.regex['upperOnly'].test(original)) {
return this.lg(original.length*this.LOWER);
}
else if (this.regex['upper'].test(original) && this.regex['upper'].test(original)) {
return this.lg(original.length*(this.LOWER + this.UPPER));
}
return this.calculateBruteForceEntropy(original);
},
/**
* Calculate entropy for reversed sequence.
*
* @param {string} original substring
* @return {number}
*/
calculateReversedSequenceEntropy: function(original) {
return this.calculateSequenceEntropy(original) + 1;
},
/**
* Get all matched dates.
*
* Single numbers will be identified as years or day-month combinations.
* Full (and "half") dates will be recognized when using -./ as separator.
*
* @param {array}formats
* @returns {array}
*/
collectDateMatches: function() {
var matches = [];
for (var type in this.regex.date) {
var regexMatches = this.password.match(this.regex.date[type]) || [];
for (var i = 0; i < regexMatches.length; i++) {
if (regexMatches[i].length > 0) {
var start = this.password.indexOf(regexMatches[i]);
matches[matches.length] = {
pattern: regexMatches[i],
entropy: this.calculateDateEntropy(regexMatches[i], type),
start: start,
end: start + regexMatches[i].length - 1,
type: 'date'
};
}
}
}
return matches;
},
/**
* Calculate date entropy for all types.
*
* @param {string} original
* @param {string} type
* @return {number}
*/
calculateDateEntropy: function(original, type) {
switch (type) {
case 'DMY':
case 'MDY':
case 'YDM':
case 'YMD':
return this.lg(this.DAYS*this.MONTHS*10*10);
break;
case 'DMYY':
case 'MDYY':
case 'YYDM':
case 'YYMD':
return this.lg(this.DAYS*this.MONTHS*this.YEARS);
break;
case 'DM':
return this.lg(this.DAYS*this.MONTHS);
break;
case 'MY':
return this.lg(this.MONTHS*10*10);
break;
case 'MYY':
return this.lg(this.MONTHS*this.YEARS);
break;
}
return this.calculateBruteForceEntropy(original);
},
/**
* Reverse the given string.
*
* @param {string} string
* @return {string} reversed
*/
getReversedString: function(string) {
return string.split('').reverse().join('');
}
};