From a9a908cd1d635c648e985d58e1b21708819e8955 Mon Sep 17 00:00:00 2001 From: Nathan Button Date: Wed, 14 Oct 2015 09:38:47 -0600 Subject: [PATCH] Fixed spatial Entropy scoring. Added sequence scoring. --- adjacency/adjcmartix.go | 17 ++++----- match/match.go | 2 + matching/matching.go | 85 ++++++++++++++++++++++++++++++++++++----- scoring/scoring.go | 37 +++++++++++++----- 4 files changed, 114 insertions(+), 27 deletions(-) diff --git a/adjacency/adjcmartix.go b/adjacency/adjcmartix.go index 480b482..6fb5f7d 100644 --- a/adjacency/adjcmartix.go +++ b/adjacency/adjcmartix.go @@ -16,36 +16,35 @@ type AdjacencyGraph struct { var AdjacencyGph = make(map[string]AdjacencyGraph); func init() { - //todo get currentloc so that i don't have to know the whole path log.SetFlags(log.Lshortfile) - AdjacencyGph["qwerty"] = buildQwerty() - AdjacencyGph["dvorak"] = buildDvorak() - AdjacencyGph["keypad"] = buildKeypad() - AdjacencyGph["macKeypad"] = buildMacKeypad() + AdjacencyGph["qwerty"] = BuildQwerty() + AdjacencyGph["dvorak"] = BuildDvorak() + AdjacencyGph["keypad"] = BuildKeypad() + AdjacencyGph["macKeypad"] = BuildMacKeypad() } -func buildQwerty() AdjacencyGraph { +func BuildQwerty() AdjacencyGraph { data, err := zxcvbn_data.Asset("data/Qwerty.json") if err != nil { panic("Can't find asset") } return GetAdjancencyGraphFromFile(data, "qwerty") } -func buildDvorak() AdjacencyGraph { +func BuildDvorak() AdjacencyGraph { data, err := zxcvbn_data.Asset("data/Dvorak.json") if err != nil { panic("Can't find asset") } return GetAdjancencyGraphFromFile(data, "dvorak") } -func buildKeypad() AdjacencyGraph { +func BuildKeypad() AdjacencyGraph { data, err := zxcvbn_data.Asset("data/Keypad.json") if err != nil { panic("Can't find asset") } return GetAdjancencyGraphFromFile(data, "keypad") } -func buildMacKeypad() AdjacencyGraph { +func BuildMacKeypad() AdjacencyGraph { data, err := zxcvbn_data.Asset("data/MacKeypad.json") if err != nil { panic("Can't find asset") diff --git a/match/match.go b/match/match.go index 0265026..11b9e49 100644 --- a/match/match.go +++ b/match/match.go @@ -23,6 +23,8 @@ type Match struct { MatchedWord string Rank float64 DictionaryName string + DictionaryLength int + Ascending bool Turns int ShiftedCount int Entropy float64 diff --git a/matching/matching.go b/matching/matching.go index b7ff692..3f66d53 100644 --- a/matching/matching.go +++ b/matching/matching.go @@ -19,6 +19,8 @@ var ( KEYPAD_STARTING_POSITIONS int KEYPAD_AVG_DEGREE float64 L33T_TABLE adjacency.AdjacencyGraph + + SEQUENCES map[string]string ) const ( @@ -29,7 +31,7 @@ const ( func init() { - +loadFrequencyList() } func Omnimatch(password string, userInputs []string) (matches []match.Match) { @@ -54,7 +56,7 @@ func Omnimatch(password string, userInputs []string) (matches []match.Match) { func loadFrequencyList() { 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() @@ -66,13 +68,20 @@ func loadFrequencyList() { ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["dvorak"]) ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["keypad"]) ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["macKeypad"]) -// -// l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") -// L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t") + // + // l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") + // 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, spatialMatch) MATCHERS = append(MATCHERS, repeatMatch) + MATCHERS = append(MATCHERS, SequenceMatch) + } @@ -351,13 +360,13 @@ func repeatMatch(password string) []match.Match { currentStreak++ } else if currentStreak > 2 { - iPos := i-currentStreak - jPos := i-1 + iPos := i - currentStreak + jPos := i - 1 matches = append(matches, match.Match{ Pattern:"repeat", I:iPos, J:jPos, - Token:password[iPos:jPos+1], + Token:password[iPos:jPos + 1], RepeatedChar:prev}) currentStreak = 1 } else { @@ -368,7 +377,7 @@ func repeatMatch(password string) []match.Match { } if currentStreak > 2 { - iPos := i - currentStreak+1 + iPos := i - currentStreak + 1 jPos := i matches = append(matches, match.Match{ Pattern:"repeat", @@ -378,4 +387,62 @@ func repeatMatch(password string) []match.Match { RepeatedChar:prev}) } 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 } \ No newline at end of file diff --git a/scoring/scoring.go b/scoring/scoring.go index bf1b189..5d5c225 100644 --- a/scoring/scoring.go +++ b/scoring/scoring.go @@ -8,6 +8,7 @@ import ( "regexp" "github.com/nbutton23/zxcvbn-go/utils/math" "github.com/nbutton23/zxcvbn-go/matching" + "github.com/nbutton23/zxcvbn-go/adjacency" ) @@ -29,7 +30,7 @@ const ( type MinEntropyMatch struct { Password string Entropy float64 - MatchSequence []match.Match //TODO ? + MatchSequence []match.Match CrackTime float64 CrackTimeDisplay string 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 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) } else if match.Pattern == "repeat" { entropy = repeatEntropy(match) + } else if match.Pattern == "sequence" { + entropy = sequenceEntropy(match) } match.Entropy = entropy @@ -181,8 +184,8 @@ func dictionaryEntropy(match match.Match) float64 { func spatialEntropy(match match.Match) float64 { var s, d float64 if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" { - s = float64(matching.KEYBOARD_STARTING_POSITIONS) - d = matching.KEYBOARD_AVG_DEGREE + s = float64(len(adjacency.BuildQwerty().Graph)) + d = adjacency.BuildKeypad().CalculateAvgDegree() } else { s = float64(matching.KEYPAD_STARTING_POSITIONS) d = matching.KEYPAD_AVG_DEGREE @@ -190,12 +193,12 @@ func spatialEntropy(match match.Match) float64 { possibilities := float64(0) - lenght := float64(len(match.Token)) + length := float64(len(match.Token)) t := match.Turns //TODO: Should this be <= or just < ? - //Estimate the number of possible patterns w/ lenght L or less with t turns or less - for i := float64(2); i <= lenght + 1; i++ { + //Estimate the number of possible patterns w/ length L or less with t turns or less + for i := float64(2); i <= length + 1; i++ { possibleTurns := math.Min(float64(t), i - 1) for j := float64(1); j <= possibleTurns + 1; 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) - //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. if S := float64(match.ShiftedCount); S > float64(0) { possibilities = float64(0) - U := lenght - S + U := length - S for i := float64(0); i < math.Min(S, U) + 1; i++ { possibilities += zxcvbn_math.NChoseK(S + U, i) @@ -221,6 +223,23 @@ func spatialEntropy(match match.Match) float64 { 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 { word := match.Token