From 0270356a6011768e20267a660fc9289b290a2d7f Mon Sep 17 00:00:00 2001 From: Nathan Button Date: Tue, 6 Oct 2015 16:15:33 -0600 Subject: [PATCH 1/2] Implemented repeating match. Started with l33t check --- adjacency/L33t.json | 51 +++++++++++++++++++++++ adjacency/adjcmartix.go | 2 +- match/match.go | 1 + matching/matching.go | 89 +++++++++++++++++++++++++++++++++++++++-- scoring/scoring.go | 32 +++++++++++---- zxcvbn.go | 2 +- 6 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 adjacency/L33t.json diff --git a/adjacency/L33t.json b/adjacency/L33t.json new file mode 100644 index 0000000..73967bf --- /dev/null +++ b/adjacency/L33t.json @@ -0,0 +1,51 @@ +{ + "graph": { + "a": [ + "4", + "@" + ], + "b": [ + "8" + ], + "c": [ + "(", + "{", + "[", + "<" + ], + "e": [ + "3" + ], + "g": [ + "6", + "9" + ], + "i": [ + "1", + "!", + "|" + ], + "l": [ + "1", + "|", + "7" + ], + "o": [ + "0" + ], + "s": [ + "$", + "5" + ], + "t": [ + "+", + "7" + ], + "x": [ + "%" + ], + "z": [ + "2" + ] + } +} diff --git a/adjacency/adjcmartix.go b/adjacency/adjcmartix.go index 6ed22e7..0af0bf5 100644 --- a/adjacency/adjcmartix.go +++ b/adjacency/adjcmartix.go @@ -9,7 +9,7 @@ import ( type AdjacencyGraph struct { - Graph map[string][6]string + Graph map[string][]string averageDegree float64 Name string } diff --git a/match/match.go b/match/match.go index 2a82197..0265026 100644 --- a/match/match.go +++ b/match/match.go @@ -26,6 +26,7 @@ type Match struct { Turns int ShiftedCount int Entropy float64 + RepeatedChar string } type DateMatch struct { diff --git a/matching/matching.go b/matching/matching.go index 432b5b8..94a6820 100644 --- a/matching/matching.go +++ b/matching/matching.go @@ -8,6 +8,7 @@ import ( "zxcvbn-go/adjacency" "zxcvbn-go/match" "sort" +// "github.com/deckarep/golang-set" ) var ( @@ -18,6 +19,7 @@ var ( KEYBOARD_AVG_DEGREE float64 KEYPAD_STARTING_POSITIONS int KEYPAD_AVG_DEGREE float64 + L33T_TABLE adjacency.AdjacencyGraph ) const ( @@ -82,8 +84,13 @@ func loadFrequencyList() { ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, keypadGraph) ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.GetAdjancencyGraphFromFile(macKeypadfilePath, "macKepad")) + l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") + L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t") + MATCHERS = append(MATCHERS, DICTIONARY_MATCHERS...) - MATCHERS = append(MATCHERS, SpatialMatch) + MATCHERS = append(MATCHERS, spatialMatch) + MATCHERS = append(MATCHERS, repeatMatch) + } @@ -246,7 +253,7 @@ func buildDateMatchCandidateTwo(day, month byte, year string, i, j int) match.Da } -func SpatialMatch(password string) (matches []match.Match) { +func spatialMatch(password string) (matches []match.Match) { for _, graph := range ADJACENCY_GRAPHS { matches = append(matches, spatialMatchHelper(password, graph)...) } @@ -299,7 +306,6 @@ func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matche j += 1 } else { // otherwise push the pattern discovered so far, if any... - // don't consider length 1 or 2 chains. if j - i > 2 { matches = append(matches, match.Match{Pattern:"spatial", I:i, J:j - 1, Token:password[i:j], DictionaryName:graph.Name, Turns:turns, ShiftedCount:shiftedCount }) @@ -313,3 +319,80 @@ func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matche } return matches } + +func relevantL33tSubtable(password string) adjacency.AdjacencyGraph { + var releventSubs adjacency.AdjacencyGraph + for _, char := range password { + if len(L33T_TABLE.Graph[string(char)]) > 0 { + releventSubs.Graph[string(char)] = L33T_TABLE.Graph[string(char)] + } + } + + return releventSubs +} + +//TODO yeah this is a little harder than i expect. . . +//func enumerateL33tSubs(table adjacency.AdjacencyGraph) []string { +// var subs [][]string +// +// dedup := func(subs []string) []string { +// deduped := mapset.NewSetFromSlice(subs) +// return deduped.ToSlice() +// } +// +// for i,v := range table.Graph { +// var nextSubs []string +// for _, subChar := range v { +// +// } +// +// } +//} + +func repeatMatch(password string) []match.Match { + var matches []match.Match + + //Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current + var current, prev string + currentStreak := 1 + var i int + var char rune + for i, char = range password { + current = string(char) + if i == 0 { + prev = current + continue + } + + if current == prev { + currentStreak++ + + } else if currentStreak > 2 { + iPos := i-currentStreak + jPos := i-1 + matches = append(matches, match.Match{ + Pattern:"repeat", + I:iPos, + J:jPos, + Token:password[iPos:jPos+1], + RepeatedChar:prev}) + currentStreak = 1 + } else { + currentStreak = 1 + } + + prev = current + } + + if currentStreak > 2 { + iPos := i - currentStreak+1 + jPos := i + matches = append(matches, match.Match{ + Pattern:"repeat", + I:iPos, + J:jPos, + Token:password[iPos:jPos + 1], + RepeatedChar:prev}) + } + return matches +} \ No newline at end of file diff --git a/scoring/scoring.go b/scoring/scoring.go index 9d74d54..222d138 100644 --- a/scoring/scoring.go +++ b/scoring/scoring.go @@ -15,9 +15,16 @@ const ( START_UPPER string = `^[A-Z][^A-Z]+$` END_UPPER string = `^[^A-Z]+[A-Z]$'` ALL_UPPER string = `^[A-Z]+$` + +//for a hash function like bcrypt/scrypt/PBKDF2, 10ms per guess is a safe lower bound. +//(usually a guess would take longer -- this assumes fast hardware and a small work factor.) +//adjust for your site accordingly if you use another hash function, possibly by +//several orders of magnitude! SINGLE_GUESS float64 = 0.010 - NUM_ATTACKERS float64 = 100 + NUM_ATTACKERS float64 = 100 //Cores used to make guesses SECONDS_PER_GUESS float64 = SINGLE_GUESS / NUM_ATTACKERS + + ) type MinEntropyMatch struct { Password string @@ -126,18 +133,18 @@ func get(a []float64, i int) float64 { return a[i] } -func calcBruteforceCardinality(password string) int { - lower, upper, digits, symbols := 0, 0, 0, 0 +func calcBruteforceCardinality(password string) float64 { + lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0) for _, char := range password { if unicode.IsLower(char) { - lower = 26 + lower = float64(26) } else if unicode.IsDigit(char) { - digits = 10 + digits = float64(10) } else if unicode.IsUpper(char) { - upper = 26 + upper = float64(26) } else { - symbols = 33 + symbols = float64(33) } } @@ -155,6 +162,8 @@ func calcEntropy(match match.Match) float64 { entropy = dictionaryEntropy(match) } else if match.Pattern == "spatial" { entropy = spatialEntropy(match) + } else if match.Pattern == "repeat" { + entropy = repeatEntropy(match) } match.Entropy = entropy @@ -264,6 +273,13 @@ func extraUpperCaseEntropy(match match.Match) float64 { return float64(math.Log2(possibililities)) } +func repeatEntropy(match match.Match) float64 { + cardinality := calcBruteforceCardinality(match.Token) + entropy := math.Log2(cardinality * float64(len(match.Token))) + + return entropy +} + func entropyToCrackTime(entropy float64) float64 { crackTime := (0.5 * math.Pow(float64(2), entropy)) * SECONDS_PER_GUESS @@ -275,7 +291,7 @@ func roundToXDigits(number float64, digits int) float64 { } func displayTime(seconds float64) string { - formater := "%d %s" + formater := "%.1f %s" minute := float64(60) hour := minute * float64(60) day := hour * float64(24) diff --git a/zxcvbn.go b/zxcvbn.go index 6d56c26..02baf49 100644 --- a/zxcvbn.go +++ b/zxcvbn.go @@ -9,7 +9,7 @@ import ( ) func main() { - password :="qw@!abcdPLSB$6D" + password :="Testaaatyhg890l33t" fmt.Println(PasswordStrength(password, nil)) } From 0c1d96fc2cd678fd5dcc891295fafcea08cc6ab2 Mon Sep 17 00:00:00 2001 From: Nathan Button Date: Tue, 6 Oct 2015 16:19:04 -0600 Subject: [PATCH 2/2] pulled out main function. --- zxcvbn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zxcvbn.go b/zxcvbn.go index 02baf49..0f00d9b 100644 --- a/zxcvbn.go +++ b/zxcvbn.go @@ -8,10 +8,10 @@ import ( "zxcvbn-go/utils/math" ) -func main() { - password :="Testaaatyhg890l33t" - fmt.Println(PasswordStrength(password, nil)) -} +//func main() { +// password :="Testaaatyhg890l33t" +// fmt.Println(PasswordStrength(password, nil)) +//} func PasswordStrength(password string, userInputs []string) scoring.MinEntropyMatch { start := time.Now()