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

1146 lines
44 KiB
JavaScript
Raw Normal View History

2018-10-16 16:28:42 +00:00
/**
* 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('');
}
};