1146 lines
44 KiB
JavaScript
1146 lines
44 KiB
JavaScript
|
/**
|
|||
|
* 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('');
|
|||
|
}
|
|||
|
};
|