zxcvbn
zxcvbn copied to clipboard
Repeated combining diacritics cause unexpected zero score
When a combining diacritic character is repeated in consecutive runes, the PasswordStrength
function gives a score of 0. For example the string below where the \u0300 rune appears twice after the initial A:
s := "A\u0300\u0300HLUMoB8g3kqgpjc3"
result := zxcvbn.PasswordStrength(s, nil)
// result.Score == 0
However, if the repeated combining characters are at the end of the string the score is still high:
s := "HLUMoB8g3kqgpjc3A\u0300\u0300"
result := zxcvbn.PasswordStrength(s, nil)
// result.Score == 4
I've not been able to look into this very deeply but disabling the repeatMatch
in Omnimatch
results in score of 4.
Hi,
Our handling of non-ascii passwords is indeed not ideal. The bug might be either in repeatMatch or in the way we compute the scoring... Both parts should probably be rewritten to work on runes (but the rewrite is probably not trivial).
By curiosity: have you tried running this test with the "upstream" dropbox zxcvbn library ? I'm curious about the result.
Hi! It seems the dropbox zxcvbn node package gives score of 4 for both strings I used in the description:
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
}
}
> var zxcvbn = require('zxcvbn');
undefined
> zxcvbn('HLUMoB8g3kqgpjc3A\u0300\u0300');
{
password: 'HLUMoB8g3kqgpjc3À̀',
guesses: 10000000000000000000,
guesses_log10: 19,
sequence: [
{
pattern: 'bruteforce',
token: 'HLUMoB8g3kqgpjc3À̀',
i: 0,
j: 18,
guesses: 10000000000000000000,
guesses_log10: 19
}
],
calc_time: 6,
crack_times_seconds: {
online_throttling_100_per_hour: 360000000000000000000,
online_no_throttling_10_per_second: 1000000000000000000,
offline_slow_hashing_1e4_per_second: 1000000000000000,
offline_fast_hashing_1e10_per_second: 1000000000
},
crack_times_display: {
online_throttling_100_per_hour: 'centuries',
online_no_throttling_10_per_second: 'centuries',
offline_slow_hashing_1e4_per_second: 'centuries',
offline_fast_hashing_1e10_per_second: '31 years'
},
score: 4,
feedback: { warning: '', suggestions: [] }
}
> zxcvbn('A\u0300\u0300HLUMoB8g3kqgpjc3');
{
password: 'À̀HLUMoB8g3kqgpjc3',
guesses: 10000000000000000000,
guesses_log10: 19,
sequence: [
{
pattern: 'bruteforce',
token: 'À̀HLUMoB8g3kqgpjc3',
i: 0,
j: 18,
guesses: 10000000000000000000,
guesses_log10: 19
}
],
calc_time: 4,
crack_times_seconds: {
online_throttling_100_per_hour: 360000000000000000000,
online_no_throttling_10_per_second: 1000000000000000000,
offline_slow_hashing_1e4_per_second: 1000000000000000,
offline_fast_hashing_1e10_per_second: 1000000000
},
crack_times_display: {
online_throttling_100_per_hour: 'centuries',
online_no_throttling_10_per_second: 'centuries',
offline_slow_hashing_1e4_per_second: 'centuries',
offline_fast_hashing_1e10_per_second: '31 years'
},
score: 4,
feedback: { warning: '', suggestions: [] }
}