mirror of
https://github.com/status-im/zxcvbn-go.git
synced 2025-01-31 13:45:02 +00:00
Fixed spatial Entropy scoring. Added sequence scoring.
This commit is contained in:
parent
80ad1b1d96
commit
a9a908cd1d
@ -16,36 +16,35 @@ type AdjacencyGraph struct {
|
|||||||
|
|
||||||
var AdjacencyGph = make(map[string]AdjacencyGraph);
|
var AdjacencyGph = make(map[string]AdjacencyGraph);
|
||||||
func init() {
|
func init() {
|
||||||
//todo get currentloc so that i don't have to know the whole path
|
|
||||||
log.SetFlags(log.Lshortfile)
|
log.SetFlags(log.Lshortfile)
|
||||||
AdjacencyGph["qwerty"] = buildQwerty()
|
AdjacencyGph["qwerty"] = BuildQwerty()
|
||||||
AdjacencyGph["dvorak"] = buildDvorak()
|
AdjacencyGph["dvorak"] = BuildDvorak()
|
||||||
AdjacencyGph["keypad"] = buildKeypad()
|
AdjacencyGph["keypad"] = BuildKeypad()
|
||||||
AdjacencyGph["macKeypad"] = buildMacKeypad()
|
AdjacencyGph["macKeypad"] = BuildMacKeypad()
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildQwerty() AdjacencyGraph {
|
func BuildQwerty() AdjacencyGraph {
|
||||||
data, err := zxcvbn_data.Asset("data/Qwerty.json")
|
data, err := zxcvbn_data.Asset("data/Qwerty.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Can't find asset")
|
panic("Can't find asset")
|
||||||
}
|
}
|
||||||
return GetAdjancencyGraphFromFile(data, "qwerty")
|
return GetAdjancencyGraphFromFile(data, "qwerty")
|
||||||
}
|
}
|
||||||
func buildDvorak() AdjacencyGraph {
|
func BuildDvorak() AdjacencyGraph {
|
||||||
data, err := zxcvbn_data.Asset("data/Dvorak.json")
|
data, err := zxcvbn_data.Asset("data/Dvorak.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Can't find asset")
|
panic("Can't find asset")
|
||||||
}
|
}
|
||||||
return GetAdjancencyGraphFromFile(data, "dvorak")
|
return GetAdjancencyGraphFromFile(data, "dvorak")
|
||||||
}
|
}
|
||||||
func buildKeypad() AdjacencyGraph {
|
func BuildKeypad() AdjacencyGraph {
|
||||||
data, err := zxcvbn_data.Asset("data/Keypad.json")
|
data, err := zxcvbn_data.Asset("data/Keypad.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Can't find asset")
|
panic("Can't find asset")
|
||||||
}
|
}
|
||||||
return GetAdjancencyGraphFromFile(data, "keypad")
|
return GetAdjancencyGraphFromFile(data, "keypad")
|
||||||
}
|
}
|
||||||
func buildMacKeypad() AdjacencyGraph {
|
func BuildMacKeypad() AdjacencyGraph {
|
||||||
data, err := zxcvbn_data.Asset("data/MacKeypad.json")
|
data, err := zxcvbn_data.Asset("data/MacKeypad.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Can't find asset")
|
panic("Can't find asset")
|
||||||
|
@ -23,6 +23,8 @@ type Match struct {
|
|||||||
MatchedWord string
|
MatchedWord string
|
||||||
Rank float64
|
Rank float64
|
||||||
DictionaryName string
|
DictionaryName string
|
||||||
|
DictionaryLength int
|
||||||
|
Ascending bool
|
||||||
Turns int
|
Turns int
|
||||||
ShiftedCount int
|
ShiftedCount int
|
||||||
Entropy float64
|
Entropy float64
|
||||||
|
@ -19,6 +19,8 @@ var (
|
|||||||
KEYPAD_STARTING_POSITIONS int
|
KEYPAD_STARTING_POSITIONS int
|
||||||
KEYPAD_AVG_DEGREE float64
|
KEYPAD_AVG_DEGREE float64
|
||||||
L33T_TABLE adjacency.AdjacencyGraph
|
L33T_TABLE adjacency.AdjacencyGraph
|
||||||
|
|
||||||
|
SEQUENCES map[string]string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,7 +31,7 @@ const (
|
|||||||
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
loadFrequencyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Omnimatch(password string, userInputs []string) (matches []match.Match) {
|
func Omnimatch(password string, userInputs []string) (matches []match.Match) {
|
||||||
@ -54,7 +56,7 @@ func Omnimatch(password string, userInputs []string) (matches []match.Match) {
|
|||||||
func loadFrequencyList() {
|
func loadFrequencyList() {
|
||||||
|
|
||||||
for n, list := range frequency.FrequencyLists {
|
for n, list := range frequency.FrequencyLists {
|
||||||
DICTIONARY_MATCHERS = append(DICTIONARY_MATCHERS,buildDictMatcher(n, buildRankedDict(list.List)))
|
DICTIONARY_MATCHERS = append(DICTIONARY_MATCHERS, buildDictMatcher(n, buildRankedDict(list.List)))
|
||||||
}
|
}
|
||||||
|
|
||||||
KEYBOARD_AVG_DEGREE = adjacency.AdjacencyGph["querty"].CalculateAvgDegree()
|
KEYBOARD_AVG_DEGREE = adjacency.AdjacencyGph["querty"].CalculateAvgDegree()
|
||||||
@ -66,13 +68,20 @@ func loadFrequencyList() {
|
|||||||
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["dvorak"])
|
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["dvorak"])
|
||||||
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["keypad"])
|
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["keypad"])
|
||||||
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["macKeypad"])
|
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["macKeypad"])
|
||||||
//
|
//
|
||||||
// l33tFilePath, _ := filepath.Abs("adjacency/L33t.json")
|
// l33tFilePath, _ := filepath.Abs("adjacency/L33t.json")
|
||||||
// L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t")
|
// L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t")
|
||||||
|
|
||||||
|
SEQUENCES = make(map[string]string)
|
||||||
|
SEQUENCES["lower"] = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
SEQUENCES["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
SEQUENCES["digits"] = "0123456789"
|
||||||
|
|
||||||
MATCHERS = append(MATCHERS, DICTIONARY_MATCHERS...)
|
MATCHERS = append(MATCHERS, DICTIONARY_MATCHERS...)
|
||||||
MATCHERS = append(MATCHERS, spatialMatch)
|
MATCHERS = append(MATCHERS, spatialMatch)
|
||||||
MATCHERS = append(MATCHERS, repeatMatch)
|
MATCHERS = append(MATCHERS, repeatMatch)
|
||||||
|
MATCHERS = append(MATCHERS, SequenceMatch)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,13 +360,13 @@ func repeatMatch(password string) []match.Match {
|
|||||||
currentStreak++
|
currentStreak++
|
||||||
|
|
||||||
} else if currentStreak > 2 {
|
} else if currentStreak > 2 {
|
||||||
iPos := i-currentStreak
|
iPos := i - currentStreak
|
||||||
jPos := i-1
|
jPos := i - 1
|
||||||
matches = append(matches, match.Match{
|
matches = append(matches, match.Match{
|
||||||
Pattern:"repeat",
|
Pattern:"repeat",
|
||||||
I:iPos,
|
I:iPos,
|
||||||
J:jPos,
|
J:jPos,
|
||||||
Token:password[iPos:jPos+1],
|
Token:password[iPos:jPos + 1],
|
||||||
RepeatedChar:prev})
|
RepeatedChar:prev})
|
||||||
currentStreak = 1
|
currentStreak = 1
|
||||||
} else {
|
} else {
|
||||||
@ -368,7 +377,7 @@ func repeatMatch(password string) []match.Match {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if currentStreak > 2 {
|
if currentStreak > 2 {
|
||||||
iPos := i - currentStreak+1
|
iPos := i - currentStreak + 1
|
||||||
jPos := i
|
jPos := i
|
||||||
matches = append(matches, match.Match{
|
matches = append(matches, match.Match{
|
||||||
Pattern:"repeat",
|
Pattern:"repeat",
|
||||||
@ -378,4 +387,62 @@ func repeatMatch(password string) []match.Match {
|
|||||||
RepeatedChar:prev})
|
RepeatedChar:prev})
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
func SequenceMatch(password string) []match.Match {
|
||||||
|
var matches []match.Match
|
||||||
|
for i := 0; i < len(password); {
|
||||||
|
j := i + 1
|
||||||
|
var seq string
|
||||||
|
var seqName string
|
||||||
|
seqDirection := 0
|
||||||
|
for seqCandidateName, seqCandidate := range SEQUENCES {
|
||||||
|
iN := strings.Index(seqCandidate, string(password[i]))
|
||||||
|
var jN int
|
||||||
|
if j < len(password) {
|
||||||
|
jN = strings.Index(seqCandidate, string(password[j]))
|
||||||
|
} else {
|
||||||
|
jN = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if iN > -1 && jN > -1 {
|
||||||
|
direction := jN - iN
|
||||||
|
if direction == 1 || direction == -1 {
|
||||||
|
seq = seqCandidate
|
||||||
|
seqName = seqCandidateName
|
||||||
|
seqDirection = direction
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if seq != "" {
|
||||||
|
for ;; {
|
||||||
|
var prevN, curN int
|
||||||
|
if j < len(password) {
|
||||||
|
prevChar, curChar := password[j - 1], password[j]
|
||||||
|
prevN, curN = strings.Index(seq, string(prevChar)), strings.Index(seq, string(curChar))
|
||||||
|
}
|
||||||
|
|
||||||
|
if j == len(password) || curN - prevN != seqDirection {
|
||||||
|
if j - i > 2 {
|
||||||
|
matches = append(matches, match.Match{Pattern:"sequence",
|
||||||
|
I:i,
|
||||||
|
J:j-1,
|
||||||
|
Token:password[i:j],
|
||||||
|
DictionaryName:seqName,
|
||||||
|
DictionaryLength: len(seq),
|
||||||
|
Ascending:(seqDirection == 1)})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
return matches
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"github.com/nbutton23/zxcvbn-go/utils/math"
|
"github.com/nbutton23/zxcvbn-go/utils/math"
|
||||||
"github.com/nbutton23/zxcvbn-go/matching"
|
"github.com/nbutton23/zxcvbn-go/matching"
|
||||||
|
"github.com/nbutton23/zxcvbn-go/adjacency"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ const (
|
|||||||
type MinEntropyMatch struct {
|
type MinEntropyMatch struct {
|
||||||
Password string
|
Password string
|
||||||
Entropy float64
|
Entropy float64
|
||||||
MatchSequence []match.Match //TODO ?
|
MatchSequence []match.Match
|
||||||
CrackTime float64
|
CrackTime float64
|
||||||
CrackTimeDisplay string
|
CrackTimeDisplay string
|
||||||
Score int
|
Score int
|
||||||
@ -37,7 +38,7 @@ type MinEntropyMatch struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns minimum entropy TODO
|
Returns minimum entropy
|
||||||
|
|
||||||
Takes a list of overlapping matches, returns the non-overlapping sublist with
|
Takes a list of overlapping matches, returns the non-overlapping sublist with
|
||||||
minimum entropy. O(nm) dp alg for length-n password with m candidate matches.
|
minimum entropy. O(nm) dp alg for length-n password with m candidate matches.
|
||||||
@ -164,6 +165,8 @@ func calcEntropy(match match.Match) float64 {
|
|||||||
entropy = spatialEntropy(match)
|
entropy = spatialEntropy(match)
|
||||||
} else if match.Pattern == "repeat" {
|
} else if match.Pattern == "repeat" {
|
||||||
entropy = repeatEntropy(match)
|
entropy = repeatEntropy(match)
|
||||||
|
} else if match.Pattern == "sequence" {
|
||||||
|
entropy = sequenceEntropy(match)
|
||||||
}
|
}
|
||||||
|
|
||||||
match.Entropy = entropy
|
match.Entropy = entropy
|
||||||
@ -181,8 +184,8 @@ func dictionaryEntropy(match match.Match) float64 {
|
|||||||
func spatialEntropy(match match.Match) float64 {
|
func spatialEntropy(match match.Match) float64 {
|
||||||
var s, d float64
|
var s, d float64
|
||||||
if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" {
|
if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" {
|
||||||
s = float64(matching.KEYBOARD_STARTING_POSITIONS)
|
s = float64(len(adjacency.BuildQwerty().Graph))
|
||||||
d = matching.KEYBOARD_AVG_DEGREE
|
d = adjacency.BuildKeypad().CalculateAvgDegree()
|
||||||
} else {
|
} else {
|
||||||
s = float64(matching.KEYPAD_STARTING_POSITIONS)
|
s = float64(matching.KEYPAD_STARTING_POSITIONS)
|
||||||
d = matching.KEYPAD_AVG_DEGREE
|
d = matching.KEYPAD_AVG_DEGREE
|
||||||
@ -190,12 +193,12 @@ func spatialEntropy(match match.Match) float64 {
|
|||||||
|
|
||||||
possibilities := float64(0)
|
possibilities := float64(0)
|
||||||
|
|
||||||
lenght := float64(len(match.Token))
|
length := float64(len(match.Token))
|
||||||
t := match.Turns
|
t := match.Turns
|
||||||
|
|
||||||
//TODO: Should this be <= or just < ?
|
//TODO: Should this be <= or just < ?
|
||||||
//Estimate the number of possible patterns w/ lenght L or less with t turns or less
|
//Estimate the number of possible patterns w/ length L or less with t turns or less
|
||||||
for i := float64(2); i <= lenght + 1; i++ {
|
for i := float64(2); i <= length + 1; i++ {
|
||||||
possibleTurns := math.Min(float64(t), i - 1)
|
possibleTurns := math.Min(float64(t), i - 1)
|
||||||
for j := float64(1); j <= possibleTurns + 1; j++ {
|
for j := float64(1); j <= possibleTurns + 1; j++ {
|
||||||
x := zxcvbn_math.NChoseK(i - 1, j - 1) * s * math.Pow(d, j)
|
x := zxcvbn_math.NChoseK(i - 1, j - 1) * s * math.Pow(d, j)
|
||||||
@ -204,13 +207,12 @@ func spatialEntropy(match match.Match) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entropy := math.Log2(possibilities)
|
entropy := math.Log2(possibilities)
|
||||||
|
|
||||||
//add extra entropu for shifted keys. ( % instead of 5 A instead of a)
|
//add extra entropu for shifted keys. ( % instead of 5 A instead of a)
|
||||||
//Math is similar to extra entropy for uppercase letters in dictionary matches.
|
//Math is similar to extra entropy for uppercase letters in dictionary matches.
|
||||||
|
|
||||||
if S := float64(match.ShiftedCount); S > float64(0) {
|
if S := float64(match.ShiftedCount); S > float64(0) {
|
||||||
possibilities = float64(0)
|
possibilities = float64(0)
|
||||||
U := lenght - S
|
U := length - S
|
||||||
|
|
||||||
for i := float64(0); i < math.Min(S, U) + 1; i++ {
|
for i := float64(0); i < math.Min(S, U) + 1; i++ {
|
||||||
possibilities += zxcvbn_math.NChoseK(S + U, i)
|
possibilities += zxcvbn_math.NChoseK(S + U, i)
|
||||||
@ -221,6 +223,23 @@ func spatialEntropy(match match.Match) float64 {
|
|||||||
|
|
||||||
return entropy
|
return entropy
|
||||||
}
|
}
|
||||||
|
func sequenceEntropy(match match.Match) float64 {
|
||||||
|
firstChar := match.Token[0]
|
||||||
|
baseEntropy := float64(0)
|
||||||
|
if string(firstChar) == "a" || string(firstChar) == "1" {
|
||||||
|
baseEntropy = float64(0)
|
||||||
|
} else {
|
||||||
|
baseEntropy = math.Log2(float64(match.DictionaryLength))
|
||||||
|
if unicode.IsUpper(rune(firstChar)) {
|
||||||
|
baseEntropy ++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match.Ascending {
|
||||||
|
baseEntropy++
|
||||||
|
}
|
||||||
|
return baseEntropy + math.Log2(float64(len(match.Token)))
|
||||||
|
}
|
||||||
func extraUpperCaseEntropy(match match.Match) float64 {
|
func extraUpperCaseEntropy(match match.Match) float64 {
|
||||||
word := match.Token
|
word := match.Token
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user