I may well be missing something here, but this example appears to be very insecure: -
const userInputs = ["Brian Thompson"]
I've ran the following tests and there are the result: -
TEST 1
Password: "Brian Thompson"
zxcvbnResult : {
calcTime: 138,
password: 'Brian Thompson',
guesses: 456,
guessesLog10: 2.658964842664435,
sequence: [
{
pattern: 'dictionary',
i: 0,
j: 13,
token: 'Brian Thompson',
matchedWord: 'brian thompson',
rank: 5,
dictionaryName: 'userInputs',
reversed: false,
l33t: false,
baseGuesses: 5,
uppercaseVariations: 91,
l33tVariations: 1,
guesses: 455,
guessesLog10: 2.658011396657112
}
],
crackTimesSeconds: {
onlineThrottling100PerHour: 16416,
onlineNoThrottling10PerSecond: 45.6,
offlineSlowHashing1e4PerSecond: 0.0456,
offlineFastHashing1e10PerSecond: 4.56e-8
},
crackTimesDisplay: {
onlineThrottling100PerHour: '5 hours',
onlineNoThrottling10PerSecond: '46 seconds',
offlineSlowHashing1e4PerSecond: 'less than a second',
offlineFastHashing1e10PerSecond: 'less than a second'
},
score: 0,
feedback: {
warning: 'There should not be any personal or page related data.',
suggestions: [ 'Add more words that are less common.' ]
}
}
TEST 2
Password: "Brian Thompson " (note the space at the end)
zxcvbnResult : {
calcTime: 138,
password: 'Brian Thompson ',
guesses: 20010,
guessesLog10: 4.30124708863621,
sequence: [
{
pattern: 'dictionary',
i: 0,
j: 13,
token: 'Brian Thompson',
matchedWord: 'brian thompson',
rank: 5,
dictionaryName: 'userInputs',
reversed: false,
l33t: false,
baseGuesses: 5,
uppercaseVariations: 91,
l33tVariations: 1,
guesses: 455,
guessesLog10: 2.658011396657112
},
{
pattern: 'bruteforce',
token: ' ',
i: 14,
j: 14,
guesses: 11,
guessesLog10: 1.041392685158225
}
],
crackTimesSeconds: {
onlineThrottling100PerHour: 720360,
onlineNoThrottling10PerSecond: 2001,
offlineSlowHashing1e4PerSecond: 2.001,
offlineFastHashing1e10PerSecond: 0.000002001
},
crackTimesDisplay: {
onlineThrottling100PerHour: '8 days',
onlineNoThrottling10PerSecond: '33 minutes',
offlineSlowHashing1e4PerSecond: '2 seconds',
offlineFastHashing1e10PerSecond: 'less than a second'
},
score: 1,
feedback: {
warning: 'There should not be any personal or page related data.',
suggestions: [ 'Add more words that are less common.' ]
}
}
TEST 3
Password: "Brian Thompson password"
zxcvbnResult : {
calcTime: 364,
password: 'Brian Thompson password',
guesses: 101501500,
guessesLog10: 8.00647246034686,
sequence: [
{
pattern: 'dictionary',
i: 0,
j: 13,
token: 'Brian Thompson',
matchedWord: 'brian thompson',
rank: 5,
dictionaryName: 'userInputs',
reversed: false,
l33t: false,
baseGuesses: 5,
uppercaseVariations: 91,
l33tVariations: 1,
guesses: 455,
guessesLog10: 2.658011396657112
},
{
pattern: 'bruteforce',
token: ' ',
i: 14,
j: 14,
guesses: 11,
guessesLog10: 1.041392685158225
},
{
pattern: 'dictionary',
i: 15,
j: 22,
token: 'password',
matchedWord: 'password',
rank: 2,
dictionaryName: 'passwords',
reversed: false,
l33t: false,
baseGuesses: 2,
uppercaseVariations: 1,
l33tVariations: 1,
guesses: 50,
guessesLog10: 1.6989700043360185
}
],
crackTimesSeconds: {
onlineThrottling100PerHour: 3654054000,
onlineNoThrottling10PerSecond: 10150150,
offlineSlowHashing1e4PerSecond: 10150.15,
offlineFastHashing1e10PerSecond: 0.01015015
},
crackTimesDisplay: {
onlineThrottling100PerHour: 'centuries',
onlineNoThrottling10PerSecond: '4 months',
offlineSlowHashing1e4PerSecond: '3 hours',
offlineFastHashing1e10PerSecond: 'less than a second'
},
score: 3,
feedback: { warning: '', suggestions: [] }
}
TEST 4
Password: "Brian Thompson 1234"
zxcvbnResult : {
calcTime: 203,
password: 'Brian Thompson 1234',
guesses: 91010000,
guessesLog10: 7.959089114367391,
sequence: [
{
pattern: 'dictionary',
i: 0,
j: 13,
token: 'Brian Thompson',
matchedWord: 'brian thompson',
rank: 5,
dictionaryName: 'userInputs',
reversed: false,
l33t: false,
baseGuesses: 5,
uppercaseVariations: 91,
l33tVariations: 1,
guesses: 455,
guessesLog10: 2.658011396657112
},
{
pattern: 'bruteforce',
token: ' 1234',
i: 14,
j: 18,
guesses: 100000,
guessesLog10: 5
}
],
crackTimesSeconds: {
onlineThrottling100PerHour: 3276360000,
onlineNoThrottling10PerSecond: 9101000,
offlineSlowHashing1e4PerSecond: 9101,
offlineFastHashing1e10PerSecond: 0.009101
},
crackTimesDisplay: {
onlineThrottling100PerHour: 'centuries',
onlineNoThrottling10PerSecond: '3 months',
offlineSlowHashing1e4PerSecond: '3 hours',
offlineFastHashing1e10PerSecond: 'less than a second'
},
score: 2,
feedback: {
warning: 'There should not be any personal or page related data.',
suggestions: [ 'Add more words that are less common.' ]
}
}
In test 1, it rightly gives a score of zero and a warning for "Brian Thompson" as "Brian Thompson" is a user input.
In test 2, a single trailing white space will give a score other than 0 and a warning.
In test 3, "Brian Thompson password" will give a score of 3 and no warnings/suggestions, despite it seems to be what should be considered a low (or lower) entropy password assuming the attacker has the user's name. This would seem to be an error that should be addressed as I think it creates opportunity for weak passwords.
In test 4, a password of "Brian Thompson 1234" - which I think we should consider to have a similar entropy/obviousness to that of test 3 - returns a score of 2 with warnings/suggestions. Is this not inconsistent with test 3's feedback?
Also, should there not just be a case for the failing of a password if it has "Brian Thompson" or a l33t of "Brian Thompson" anywhere in the password string? Should we not consider user input + " password" to be rather obvious?
The user can write their own function to check the password for substrings of user inputs but this is 1) hackish 2) difficult to take into account l33t as l33t would have to be done by a separate package (unless the zxcvbn-ts l33t function can be made available for direct import).
Thoughts?