Merge pull request #32 from nbutton23/clean-up-and-formating

Passing Lint checks
This commit is contained in:
Nathan Button 2018-08-30 15:18:52 -06:00 committed by GitHub
commit be3c235ccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 199 additions and 197 deletions

View File

@ -20,7 +20,6 @@ jobs:
working_directory: /go/src/github.com/nbutton23/zxcvbn-go working_directory: /go/src/github.com/nbutton23/zxcvbn-go
steps: steps:
- checkout - checkout
- run: go get -u golang.org/x/lint/golint
# specify any bash command here prefixed with `run: `
- run: make test - run: make test
# - run: make lint - run: make lint

View File

@ -3,65 +3,76 @@ package adjacency
import ( import (
"encoding/json" "encoding/json"
"log" "log"
// "fmt"
"github.com/nbutton23/zxcvbn-go/data" "github.com/nbutton23/zxcvbn-go/data"
) )
type AdjacencyGraph struct { // Graph holds information about different graphs
type Graph struct {
Graph map[string][]string Graph map[string][]string
averageDegree float64 averageDegree float64
Name string Name string
} }
var AdjacencyGph = make(map[string]AdjacencyGraph) // GraphMap is a map of all graphs
var GraphMap = make(map[string]Graph)
func init() { func init() {
AdjacencyGph["qwerty"] = BuildQwerty() GraphMap["qwerty"] = BuildQwerty()
AdjacencyGph["dvorak"] = BuildDvorak() GraphMap["dvorak"] = BuildDvorak()
AdjacencyGph["keypad"] = BuildKeypad() GraphMap["keypad"] = BuildKeypad()
AdjacencyGph["macKeypad"] = BuildMacKeypad() GraphMap["macKeypad"] = BuildMacKeypad()
AdjacencyGph["l33t"] = BuildLeet() GraphMap["l33t"] = BuildLeet()
} }
func BuildQwerty() AdjacencyGraph { //BuildQwerty builds the Qwerty Graph
data, err := zxcvbn_data.Asset("data/Qwerty.json") func BuildQwerty() Graph {
data, err := 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 {
data, err := zxcvbn_data.Asset("data/Dvorak.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "dvorak")
}
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 {
data, err := zxcvbn_data.Asset("data/MacKeypad.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "mac_keypad")
}
func BuildLeet() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/L33t.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "keypad")
} }
func GetAdjancencyGraphFromFile(data []byte, name string) AdjacencyGraph { //BuildDvorak builds the Dvorak Graph
func BuildDvorak() Graph {
data, err := data.Asset("data/Dvorak.json")
if err != nil {
panic("Can't find asset")
}
return getAdjancencyGraphFromFile(data, "dvorak")
}
var graph AdjacencyGraph //BuildKeypad builds the Keypad Graph
func BuildKeypad() Graph {
data, err := data.Asset("data/Keypad.json")
if err != nil {
panic("Can't find asset")
}
return getAdjancencyGraphFromFile(data, "keypad")
}
//BuildMacKeypad builds the Mac Keypad Graph
func BuildMacKeypad() Graph {
data, err := data.Asset("data/MacKeypad.json")
if err != nil {
panic("Can't find asset")
}
return getAdjancencyGraphFromFile(data, "mac_keypad")
}
//BuildLeet builds the L33T Graph
func BuildLeet() Graph {
data, err := data.Asset("data/L33t.json")
if err != nil {
panic("Can't find asset")
}
return getAdjancencyGraphFromFile(data, "keypad")
}
func getAdjancencyGraphFromFile(data []byte, name string) Graph {
var graph Graph
err := json.Unmarshal(data, &graph) err := json.Unmarshal(data, &graph)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -70,10 +81,11 @@ func GetAdjancencyGraphFromFile(data []byte, name string) AdjacencyGraph {
return graph return graph
} }
// CalculateAvgDegree calclates the average degree between nodes in the graph
//on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1. //on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1.
//this calculates the average over all keys. //this calculates the average over all keys.
//TODO double check that i ported this correctly scoring.coffee ln 5 //TODO double check that i ported this correctly scoring.coffee ln 5
func (adjGrp AdjacencyGraph) CalculateAvgDegree() float64 { func (adjGrp Graph) CalculateAvgDegree() float64 {
if adjGrp.averageDegree != float64(0) { if adjGrp.averageDegree != float64(0) {
return adjGrp.averageDegree return adjGrp.averageDegree
} }

View File

@ -12,7 +12,7 @@
// data/Surnames.json // data/Surnames.json
// DO NOT EDIT! // DO NOT EDIT!
package zxcvbn_data package data
import ( import (
"bytes" "bytes"

View File

@ -10,19 +10,20 @@ import (
) )
const ( const (
START_UPPER string = `^[A-Z][^A-Z]+$` numYears = float64(119) // years match against 1900 - 2019
END_UPPER string = `^[^A-Z]+[A-Z]$'` numMonths = float64(12)
ALL_UPPER string = `^[A-Z]+$` numDays = float64(31)
NUM_YEARS = float64(119) // years match against 1900 - 2019
NUM_MONTHS = float64(12)
NUM_DAYS = float64(31)
) )
var ( var (
KEYPAD_STARTING_POSITIONS = len(adjacency.AdjacencyGph["keypad"].Graph) startUpperRx = regexp.MustCompile(`^[A-Z][^A-Z]+$`)
KEYPAD_AVG_DEGREE = adjacency.AdjacencyGph["keypad"].CalculateAvgDegree() endUpperRx = regexp.MustCompile(`^[^A-Z]+[A-Z]$'`)
allUpperRx = regexp.MustCompile(`^[A-Z]+$`)
keyPadStartingPositions = len(adjacency.GraphMap["keypad"].Graph)
keyPadAvgDegree = adjacency.GraphMap["keypad"].CalculateAvgDegree()
) )
// DictionaryEntropy calculates the entropy of a dictionary match
func DictionaryEntropy(match match.Match, rank float64) float64 { func DictionaryEntropy(match match.Match, rank float64) float64 {
baseEntropy := math.Log2(rank) baseEntropy := math.Log2(rank)
upperCaseEntropy := extraUpperCaseEntropy(match) upperCaseEntropy := extraUpperCaseEntropy(match)
@ -49,9 +50,7 @@ func extraUpperCaseEntropy(match match.Match) float64 {
//so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy. //so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy.
//allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe. //allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe.
for _, regex := range []string{START_UPPER, END_UPPER, ALL_UPPER} { for _, matcher := range []*regexp.Regexp{startUpperRx, endUpperRx, allUpperRx} {
matcher := regexp.MustCompile(regex)
if matcher.MatchString(word) { if matcher.MatchString(word) {
return float64(1) return float64(1)
} }
@ -72,7 +71,7 @@ func extraUpperCaseEntropy(match match.Match) float64 {
var possibililities float64 var possibililities float64
for i := float64(0); i <= math.Min(countUpper, countLower); i++ { for i := float64(0); i <= math.Min(countUpper, countLower); i++ {
possibililities += float64(zxcvbn_math.NChoseK(totalLenght, i)) possibililities += float64(zxcvbnmath.NChoseK(totalLenght, i))
} }
if possibililities < 1 { if possibililities < 1 {
@ -82,6 +81,7 @@ func extraUpperCaseEntropy(match match.Match) float64 {
return float64(math.Log2(possibililities)) return float64(math.Log2(possibililities))
} }
// SpatialEntropy calculates the entropy for spatial matches
func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 { func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
var s, d float64 var s, d float64
if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" { if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" {
@ -89,8 +89,8 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
s = float64(len(adjacency.BuildQwerty().Graph)) s = float64(len(adjacency.BuildQwerty().Graph))
d = adjacency.BuildQwerty().CalculateAvgDegree() d = adjacency.BuildQwerty().CalculateAvgDegree()
} else { } else {
s = float64(KEYPAD_STARTING_POSITIONS) s = float64(keyPadStartingPositions)
d = KEYPAD_AVG_DEGREE d = keyPadAvgDegree
} }
possibilities := float64(0) possibilities := float64(0)
@ -102,7 +102,7 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
for i := float64(2); i <= length+1; i++ { for i := float64(2); i <= length+1; i++ {
possibleTurns := math.Min(float64(turns), i-1) possibleTurns := math.Min(float64(turns), 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 := zxcvbnmath.NChoseK(i-1, j-1) * s * math.Pow(d, j)
possibilities += x possibilities += x
} }
} }
@ -116,7 +116,7 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
U := length - 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 += zxcvbnmath.NChoseK(S+U, i)
} }
entropy += math.Log2(possibilities) entropy += math.Log2(possibilities)
@ -125,6 +125,7 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
return entropy return entropy
} }
// RepeatEntropy calculates the entropy for repeating entropy
func RepeatEntropy(match match.Match) float64 { func RepeatEntropy(match match.Match) float64 {
cardinality := CalcBruteForceCardinality(match.Token) cardinality := CalcBruteForceCardinality(match.Token)
entropy := math.Log2(cardinality * float64(len(match.Token))) entropy := math.Log2(cardinality * float64(len(match.Token)))
@ -132,6 +133,7 @@ func RepeatEntropy(match match.Match) float64 {
return entropy return entropy
} }
// CalcBruteForceCardinality calculates the brute force cardinality
//TODO: Validate against python //TODO: Validate against python
func CalcBruteForceCardinality(password string) float64 { func CalcBruteForceCardinality(password string) float64 {
lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0) lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0)
@ -152,6 +154,7 @@ func CalcBruteForceCardinality(password string) float64 {
return cardinality return cardinality
} }
// SequenceEntropy calculates the entropy for sequences such as 4567 or cdef
func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 { func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 {
firstChar := match.Token[0] firstChar := match.Token[0]
baseEntropy := float64(0) baseEntropy := float64(0)
@ -171,6 +174,7 @@ func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) fl
return baseEntropy + math.Log2(float64(len(match.Token))) return baseEntropy + math.Log2(float64(len(match.Token)))
} }
// ExtraLeetEntropy calulates the added entropy provied by l33t substitustions
func ExtraLeetEntropy(match match.Match, password string) float64 { func ExtraLeetEntropy(match match.Match, password string) float64 {
var subsitutions float64 var subsitutions float64
var unsub float64 var unsub float64
@ -187,7 +191,7 @@ func ExtraLeetEntropy(match match.Match, password string) float64 {
var possibilities float64 var possibilities float64
for i := float64(0); i <= math.Min(subsitutions, unsub)+1; i++ { for i := float64(0); i <= math.Min(subsitutions, unsub)+1; i++ {
possibilities += zxcvbn_math.NChoseK(subsitutions+unsub, i) possibilities += zxcvbnmath.NChoseK(subsitutions+unsub, i)
} }
if possibilities <= 1 { if possibilities <= 1 {
@ -196,16 +200,13 @@ func ExtraLeetEntropy(match match.Match, password string) float64 {
return math.Log2(possibilities) return math.Log2(possibilities)
} }
func YearEntropy(dateMatch match.DateMatch) float64 { // DateEntropy calculates the entropy provided by a date
return math.Log2(NUM_YEARS)
}
func DateEntropy(dateMatch match.DateMatch) float64 { func DateEntropy(dateMatch match.DateMatch) float64 {
var entropy float64 var entropy float64
if dateMatch.Year < 100 { if dateMatch.Year < 100 {
entropy = math.Log2(NUM_DAYS * NUM_MONTHS * 100) entropy = math.Log2(numDays * numMonths * 100)
} else { } else {
entropy = math.Log2(NUM_DAYS * NUM_MONTHS * NUM_YEARS) entropy = math.Log2(numDays * numMonths * numYears)
} }
if dateMatch.Separator != "" { if dateMatch.Separator != "" {

View File

@ -2,16 +2,17 @@ package frequency
import ( import (
"encoding/json" "encoding/json"
"github.com/nbutton23/zxcvbn-go/data"
"log" "log"
)
type FrequencyList struct { "github.com/nbutton23/zxcvbn-go/data"
)
// List holds a frequency list
type List struct {
Name string Name string
List []string List []string
} }
// Lists holds all the frequency list in a map
var FrequencyLists = make(map[string]FrequencyList) var Lists = make(map[string]List)
func init() { func init() {
maleFilePath := getAsset("data/MaleNames.json") maleFilePath := getAsset("data/MaleNames.json")
@ -20,24 +21,24 @@ func init() {
englishFilePath := getAsset("data/English.json") englishFilePath := getAsset("data/English.json")
passwordsFilePath := getAsset("data/Passwords.json") passwordsFilePath := getAsset("data/Passwords.json")
FrequencyLists["MaleNames"] = GetStringListFromAsset(maleFilePath, "MaleNames") Lists["MaleNames"] = getStringListFromAsset(maleFilePath, "MaleNames")
FrequencyLists["FemaleNames"] = GetStringListFromAsset(femaleFilePath, "FemaleNames") Lists["FemaleNames"] = getStringListFromAsset(femaleFilePath, "FemaleNames")
FrequencyLists["Surname"] = GetStringListFromAsset(surnameFilePath, "Surname") Lists["Surname"] = getStringListFromAsset(surnameFilePath, "Surname")
FrequencyLists["English"] = GetStringListFromAsset(englishFilePath, "English") Lists["English"] = getStringListFromAsset(englishFilePath, "English")
FrequencyLists["Passwords"] = GetStringListFromAsset(passwordsFilePath, "Passwords") Lists["Passwords"] = getStringListFromAsset(passwordsFilePath, "Passwords")
} }
func getAsset(name string) []byte { func getAsset(name string) []byte {
data, err := zxcvbn_data.Asset(name) data, err := data.Asset(name)
if err != nil { if err != nil {
panic("Error getting asset " + name) panic("Error getting asset " + name)
} }
return data return data
} }
func GetStringListFromAsset(data []byte, name string) FrequencyList { func getStringListFromAsset(data []byte, name string) List {
var tempList FrequencyList var tempList List
err := json.Unmarshal(data, &tempList) err := json.Unmarshal(data, &tempList)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -4,6 +4,7 @@ import (
"github.com/nbutton23/zxcvbn-go" "github.com/nbutton23/zxcvbn-go"
) )
// Fuzz is used to run https://github.com/dvyukov/go-fuzz
func Fuzz(data []byte) int { func Fuzz(data []byte) int {
password := string(data) password := string(data)

View File

@ -1,5 +1,6 @@
package match package match
//Matches is an alies for []Match used for sorting
type Matches []Match type Matches []Match
func (s Matches) Len() int { func (s Matches) Len() int {
@ -18,6 +19,7 @@ func (s Matches) Less(i, j int) bool {
} }
} }
// Match represents different matches
type Match struct { type Match struct {
Pattern string Pattern string
I, J int I, J int
@ -26,6 +28,7 @@ type Match struct {
Entropy float64 Entropy float64
} }
//DateMatch is specifilly a match for type date
type DateMatch struct { type DateMatch struct {
Pattern string Pattern string
I, J int I, J int
@ -34,6 +37,7 @@ type DateMatch struct {
Day, Month, Year int64 Day, Month, Year int64
} }
//Matcher are a func and ID that can be used to match different passwords
type Matcher struct { type Matcher struct {
MatchingFunc func(password string) []Match MatchingFunc func(password string) []Match
ID string ID string

View File

@ -1,25 +1,33 @@
package matching package matching
import ( import (
"regexp"
"strconv" "strconv"
"strings" "strings"
"regexp"
"github.com/nbutton23/zxcvbn-go/entropy" "github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match" "github.com/nbutton23/zxcvbn-go/match"
) )
const ( const (
DATESEP_MATCHER_NAME = "DATESEP" dateSepMatcherName = "DATESEP"
DATEWITHOUTSEP_MATCHER_NAME = "DATEWITHOUT" dateWithOutSepMatcherName = "DATEWITHOUT"
) )
var (
dateRxYearSuffix = regexp.MustCompile( `((\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})(\s|-|\/|\\|_|\.)(19\d{2}|200\d|201\d|\d{2}))`)
dateRxYearPrefix = regexp.MustCompile(`((19\d{2}|200\d|201\d|\d{2})(\s|-|/|\\|_|\.)(\d{1,2})(\s|-|/|\\|_|\.)(\d{1,2}))`)
dateWithOutSepMatch = regexp.MustCompile( `\d{4,8}`)
)
//FilterDateSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterDateSepMatcher(m match.Matcher) bool { func FilterDateSepMatcher(m match.Matcher) bool {
return m.ID == DATESEP_MATCHER_NAME return m.ID == dateSepMatcherName
} }
//FilterDateWithoutSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterDateWithoutSepMatcher(m match.Matcher) bool { func FilterDateWithoutSepMatcher(m match.Matcher) bool {
return m.ID == DATEWITHOUTSEP_MATCHER_NAME return m.ID == dateWithOutSepMatcherName
} }
func checkDate(day, month, year int64) (bool, int64, int64, int64) { func checkDate(day, month, year int64) (bool, int64, int64, int64) {
@ -60,9 +68,8 @@ func dateSepMatchHelper(password string) []match.DateMatch {
var matches []match.DateMatch var matches []match.DateMatch
matcher := regexp.MustCompile(DATE_RX_YEAR_SUFFIX) for _, v := range dateRxYearSuffix.FindAllString(password, len(password)) {
for _, v := range matcher.FindAllString(password, len(password)) { splitV := dateRxYearSuffix.FindAllStringSubmatch(v, len(v))
splitV := matcher.FindAllStringSubmatch(v, len(v))
i := strings.Index(password, v) i := strings.Index(password, v)
j := i + len(v) j := i + len(v)
day, _ := strconv.ParseInt(splitV[0][4], 10, 16) day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
@ -72,9 +79,8 @@ func dateSepMatchHelper(password string) []match.DateMatch {
matches = append(matches, match) matches = append(matches, match)
} }
matcher = regexp.MustCompile(DATE_RX_YEAR_PREFIX) for _, v := range dateRxYearPrefix.FindAllString(password, len(password)) {
for _, v := range matcher.FindAllString(password, len(password)) { splitV := dateRxYearPrefix.FindAllStringSubmatch(v, len(v))
splitV := matcher.FindAllStringSubmatch(v, len(v))
i := strings.Index(password, v) i := strings.Index(password, v)
j := i + len(v) j := i + len(v)
day, _ := strconv.ParseInt(splitV[0][4], 10, 16) day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
@ -98,13 +104,13 @@ func dateSepMatchHelper(password string) []match.DateMatch {
} }
type DateMatchCandidate struct { type dateMatchCandidate struct {
DayMonth string DayMonth string
Year string Year string
I, J int I, J int
} }
type DateMatchCandidateTwo struct { type dateMatchCandidateTwo struct {
Day string Day string
Month string Month string
Year string Year string
@ -132,13 +138,12 @@ func dateWithoutSepMatch(password string) []match.Match {
//TODO Has issues with 6 digit dates //TODO Has issues with 6 digit dates
func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) { func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) {
matcher := regexp.MustCompile(DATE_WITHOUT_SEP_MATCH) for _, v := range dateWithOutSepMatch.FindAllString(password, len(password)) {
for _, v := range matcher.FindAllString(password, len(password)) {
i := strings.Index(password, v) i := strings.Index(password, v)
j := i + len(v) j := i + len(v)
length := len(v) length := len(v)
lastIndex := length - 1 lastIndex := length - 1
var candidatesRoundOne []DateMatchCandidate var candidatesRoundOne []dateMatchCandidate
if length <= 6 { if length <= 6 {
//2-digit year prefix //2-digit year prefix
@ -155,7 +160,7 @@ func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) {
candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j)) candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j))
} }
var candidatesRoundTwo []DateMatchCandidateTwo var candidatesRoundTwo []dateMatchCandidateTwo
for _, c := range candidatesRoundOne { for _, c := range candidatesRoundOne {
if len(c.DayMonth) == 2 { if len(c.DayMonth) == 2 {
candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:1], c.Year, c.I, c.J)) candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:1], c.Year, c.I, c.J))
@ -194,11 +199,11 @@ func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) {
return matches return matches
} }
func buildDateMatchCandidate(dayMonth, year string, i, j int) DateMatchCandidate { func buildDateMatchCandidate(dayMonth, year string, i, j int) dateMatchCandidate {
return DateMatchCandidate{DayMonth: dayMonth, Year: year, I: i, J: j} return dateMatchCandidate{DayMonth: dayMonth, Year: year, I: i, J: j}
} }
func buildDateMatchCandidateTwo(day, month string, year string, i, j int) DateMatchCandidateTwo { func buildDateMatchCandidateTwo(day, month string, year string, i, j int) dateMatchCandidateTwo {
return DateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j} return dateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j}
} }

View File

@ -7,10 +7,12 @@ import (
"github.com/nbutton23/zxcvbn-go/match" "github.com/nbutton23/zxcvbn-go/match"
) )
const L33T_MATCHER_NAME = "l33t" // L33TMatcherName id
const L33TMatcherName = "l33t"
//FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterL33tMatcher(m match.Matcher) bool { func FilterL33tMatcher(m match.Matcher) bool {
return m.ID == L33T_MATCHER_NAME return m.ID == L33TMatcherName
} }
func l33tMatch(password string) []match.Match { func l33tMatch(password string) []match.Match {
@ -19,7 +21,7 @@ func l33tMatch(password string) []match.Match {
var matches []match.Match var matches []match.Match
for _, permutation := range permutations { for _, permutation := range permutations {
for _, mather := range DICTIONARY_MATCHERS { for _, mather := range dictionaryMatchers {
matches = append(matches, mather.MatchingFunc(permutation)...) matches = append(matches, mather.MatchingFunc(permutation)...)
} }
} }
@ -45,7 +47,7 @@ func getPermutations(password string) []string {
// inside the provided password. // inside the provided password.
func relevantL33tSubtable(password string) map[string][]string { func relevantL33tSubtable(password string) map[string][]string {
relevantSubs := make(map[string][]string) relevantSubs := make(map[string][]string)
for key, values := range L33T_TABLE.Graph { for key, values := range l33tTable.Graph {
for _, value := range values { for _, value := range values {
if strings.Contains(password, value) { if strings.Contains(password, value) {
relevantSubs[key] = append(relevantSubs[key], value) relevantSubs[key] = append(relevantSubs[key], value)

View File

@ -9,28 +9,23 @@ import (
) )
var ( var (
DICTIONARY_MATCHERS []match.Matcher dictionaryMatchers []match.Matcher
MATCHERS []match.Matcher matchers []match.Matcher
ADJACENCY_GRAPHS []adjacency.AdjacencyGraph adjacencyGraphs []adjacency.Graph
L33T_TABLE adjacency.AdjacencyGraph l33tTable adjacency.Graph
SEQUENCES map[string]string sequences map[string]string
)
const (
DATE_RX_YEAR_SUFFIX string = `((\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})(\s|-|\/|\\|_|\.)(19\d{2}|200\d|201\d|\d{2}))`
DATE_RX_YEAR_PREFIX string = `((19\d{2}|200\d|201\d|\d{2})(\s|-|/|\\|_|\.)(\d{1,2})(\s|-|/|\\|_|\.)(\d{1,2}))`
DATE_WITHOUT_SEP_MATCH string = `\d{4,8}`
) )
func init() { func init() {
loadFrequencyList() loadFrequencyList()
} }
// Omnimatch runs all matchers against the password
func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) { func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) {
//Can I run into the issue where nil is not equal to nil? //Can I run into the issue where nil is not equal to nil?
if DICTIONARY_MATCHERS == nil || ADJACENCY_GRAPHS == nil { if dictionaryMatchers == nil || adjacencyGraphs == nil {
loadFrequencyList() loadFrequencyList()
} }
@ -39,7 +34,7 @@ func Omnimatch(password string, userInputs []string, filters ...func(match.Match
matches = userInputMatcher(password) matches = userInputMatcher(password)
} }
for _, matcher := range MATCHERS { for _, matcher := range matchers {
shouldBeFiltered := false shouldBeFiltered := false
for i := range filters { for i := range filters {
if filters[i](matcher) { if filters[i](matcher) {
@ -57,31 +52,31 @@ func Omnimatch(password string, userInputs []string, filters ...func(match.Match
func loadFrequencyList() { func loadFrequencyList() {
for n, list := range frequency.FrequencyLists { for n, list := range frequency.Lists {
DICTIONARY_MATCHERS = append(DICTIONARY_MATCHERS, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n}) dictionaryMatchers = append(dictionaryMatchers, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n})
} }
L33T_TABLE = adjacency.AdjacencyGph["l33t"] l33tTable = adjacency.GraphMap["l33t"]
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["qwerty"]) adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["qwerty"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["dvorak"]) adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["dvorak"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["keypad"]) adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["keypad"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["macKeypad"]) adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["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 = make(map[string]string)
SEQUENCES["lower"] = "abcdefghijklmnopqrstuvwxyz" sequences["lower"] = "abcdefghijklmnopqrstuvwxyz"
SEQUENCES["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" sequences["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
SEQUENCES["digits"] = "0123456789" sequences["digits"] = "0123456789"
MATCHERS = append(MATCHERS, DICTIONARY_MATCHERS...) matchers = append(matchers, dictionaryMatchers...)
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: spatialMatch, ID: SPATIAL_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: spatialMatch, ID: spatialMatcherName})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: repeatMatch, ID: REPEAT_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: repeatMatch, ID: repeatMatcherName})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: sequenceMatch, ID: SEQUENCE_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: sequenceMatch, ID: sequenceMatcherName})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: l33tMatch, ID: L33T_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: l33tMatch, ID: L33TMatcherName})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: dateSepMatcher, ID: DATESEP_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: dateSepMatcher, ID: dateSepMatcherName})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: DATEWITHOUTSEP_MATCHER_NAME}) matchers = append(matchers, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: dateWithOutSepMatcherName})
} }

View File

@ -106,7 +106,7 @@ func TestSpatialMatchDvorak(t *testing.T) {
func TestDictionaryMatch(t *testing.T) { func TestDictionaryMatch(t *testing.T) {
var matches []match.Match var matches []match.Match
for _, dicMatcher := range DICTIONARY_MATCHERS { for _, dicMatcher := range dictionaryMatchers {
matchesTemp := dicMatcher.MatchingFunc("first") matchesTemp := dicMatcher.MatchingFunc("first")
matches = append(matches, matchesTemp...) matches = append(matches, matchesTemp...)
} }

View File

@ -7,10 +7,11 @@ import (
"github.com/nbutton23/zxcvbn-go/match" "github.com/nbutton23/zxcvbn-go/match"
) )
const REPEAT_MATCHER_NAME = "REPEAT" const repeatMatcherName = "REPEAT"
//FilterRepeatMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterRepeatMatcher(m match.Matcher) bool { func FilterRepeatMatcher(m match.Matcher) bool {
return m.ID == REPEAT_MATCHER_NAME return m.ID == repeatMatcherName
} }
func repeatMatch(password string) []match.Match { func repeatMatch(password string) []match.Match {

View File

@ -7,10 +7,11 @@ import (
"github.com/nbutton23/zxcvbn-go/match" "github.com/nbutton23/zxcvbn-go/match"
) )
const SEQUENCE_MATCHER_NAME = "SEQ" const sequenceMatcherName = "SEQ"
//FilterSequenceMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterSequenceMatcher(m match.Matcher) bool { func FilterSequenceMatcher(m match.Matcher) bool {
return m.ID == SEQUENCE_MATCHER_NAME return m.ID == sequenceMatcherName
} }
func sequenceMatch(password string) []match.Match { func sequenceMatch(password string) []match.Match {
@ -20,7 +21,7 @@ func sequenceMatch(password string) []match.Match {
var seq string var seq string
var seqName string var seqName string
seqDirection := 0 seqDirection := 0
for seqCandidateName, seqCandidate := range SEQUENCES { for seqCandidateName, seqCandidate := range sequences {
iN := strings.Index(seqCandidate, string(password[i])) iN := strings.Index(seqCandidate, string(password[i]))
var jN int var jN int
if j < len(password) { if j < len(password) {
@ -64,7 +65,7 @@ func sequenceMatch(password string) []match.Match {
} }
break break
} else { } else {
j += 1 j++
} }
} }

View File

@ -8,14 +8,15 @@ import (
"github.com/nbutton23/zxcvbn-go/match" "github.com/nbutton23/zxcvbn-go/match"
) )
const SPATIAL_MATCHER_NAME = "SPATIAL" const spatialMatcherName = "SPATIAL"
//FilterSpatialMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
func FilterSpatialMatcher(m match.Matcher) bool { func FilterSpatialMatcher(m match.Matcher) bool {
return m.ID == SPATIAL_MATCHER_NAME return m.ID == spatialMatcherName
} }
func spatialMatch(password string) (matches []match.Match) { func spatialMatch(password string) (matches []match.Match) {
for _, graph := range ADJACENCY_GRAPHS { for _, graph := range adjacencyGraphs {
if graph.Graph != nil { if graph.Graph != nil {
matches = append(matches, spatialMatchHelper(password, graph)...) matches = append(matches, spatialMatchHelper(password, graph)...)
} }
@ -23,7 +24,7 @@ func spatialMatch(password string) (matches []match.Match) {
return matches return matches
} }
func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matches []match.Match) { func spatialMatchHelper(password string, graph adjacency.Graph) (matches []match.Match) {
for i := 0; i < len(password)-1; { for i := 0; i < len(password)-1; {
j := i + 1 j := i + 1
@ -42,7 +43,7 @@ func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matche
if j < len(password) { if j < len(password) {
curChar := password[j] curChar := password[j]
for _, adj := range adjacents { for _, adj := range adjacents {
curDirection += 1 curDirection++
if strings.Index(adj, string(curChar)) != -1 { if strings.Index(adj, string(curChar)) != -1 {
found = true found = true
@ -51,13 +52,13 @@ func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matche
if strings.Index(adj, string(curChar)) == 1 { if strings.Index(adj, string(curChar)) == 1 {
//index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc. //index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
//for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted. //for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
shiftedCount += 1 shiftedCount++
} }
if lastDirection != foundDirection { if lastDirection != foundDirection {
//adding a turn is correct even in the initial case when last_direction is null: //adding a turn is correct even in the initial case when last_direction is null:
//every spatial pattern starts with a turn. //every spatial pattern starts with a turn.
turns += 1 turns++
lastDirection = foundDirection lastDirection = foundDirection
} }
break break
@ -67,7 +68,7 @@ func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matche
//if the current pattern continued, extend j and try to grow again //if the current pattern continued, extend j and try to grow again
if found { if found {
j += 1 j++
} else { } else {
//otherwise push the pattern discovered so far, if any... //otherwise push the pattern discovered so far, if any...
//don't consider length 1 or 2 chains. //don't consider length 1 or 2 chains.

View File

@ -10,19 +10,16 @@ import (
) )
const ( 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. //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.) //(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 //adjust for your site accordingly if you use another hash function, possibly by
//several orders of magnitude! //several orders of magnitude!
SINGLE_GUESS float64 = 0.010 singleGuess float64 = 0.010
NUM_ATTACKERS float64 = 100 //Cores used to make guesses numAttackers float64 = 100 //Cores used to make guesses
SECONDS_PER_GUESS float64 = SINGLE_GUESS / NUM_ATTACKERS secondsPerGuess float64 = singleGuess / numAttackers
) )
// MinEntropyMatch is the lowest entropy match found
type MinEntropyMatch struct { type MinEntropyMatch struct {
Password string Password string
Entropy float64 Entropy float64
@ -34,7 +31,7 @@ type MinEntropyMatch struct {
} }
/* /*
Returns minimum entropy MinimumEntropyMatchSequence returns the 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.
@ -130,13 +127,13 @@ func get(a []float64, i int) float64 {
} }
func entropyToCrackTime(entropy float64) float64 { func entropyToCrackTime(entropy float64) float64 {
crackTime := (0.5 * math.Pow(float64(2), entropy)) * SECONDS_PER_GUESS crackTime := (0.5 * math.Pow(float64(2), entropy)) * secondsPerGuess
return crackTime return crackTime
} }
func roundToXDigits(number float64, digits int) float64 { func roundToXDigits(number float64, digits int) float64 {
return zxcvbn_math.Round(number, .5, digits) return zxcvbnmath.Round(number, .5, digits)
} }
func displayTime(seconds float64) string { func displayTime(seconds float64) string {

View File

@ -1,12 +1,11 @@
package zxcvbn_math package zxcvbnmath
import "math" import "math"
/** /*
NChoseK http://blog.plover.com/math/choose.html
I am surprised that I have to define these. . . Maybe i just didn't look hard enough for a lib. I am surprised that I have to define these. . . Maybe i just didn't look hard enough for a lib.
*/ */
//http://blog.plover.com/math/choose.html
func NChoseK(n, k float64) float64 { func NChoseK(n, k float64) float64 {
if k > n { if k > n {
return 0 return 0
@ -25,6 +24,7 @@ func NChoseK(n, k float64) float64 {
return r return r
} }
// Round a number
func Round(val float64, roundOn float64, places int) (newVal float64) { func Round(val float64, roundOn float64, places int) (newVal float64) {
var round float64 var round float64
pow := math.Pow(10, float64(places)) pow := math.Pow(10, float64(places))

View File

@ -1,4 +1,4 @@
package zxcvbn_math package zxcvbnmath
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"

View File

@ -9,6 +9,7 @@ import (
"github.com/nbutton23/zxcvbn-go/utils/math" "github.com/nbutton23/zxcvbn-go/utils/math"
) )
// PasswordStrength takes a password, userInputs and optional filters and returns a MinEntropyMatch
func PasswordStrength(password string, userInputs []string, filters ...func(match.Matcher) bool) scoring.MinEntropyMatch { func PasswordStrength(password string, userInputs []string, filters ...func(match.Matcher) bool) scoring.MinEntropyMatch {
start := time.Now() start := time.Now()
matches := matching.Omnimatch(password, userInputs, filters...) matches := matching.Omnimatch(password, userInputs, filters...)
@ -16,6 +17,6 @@ func PasswordStrength(password string, userInputs []string, filters ...func(matc
end := time.Now() end := time.Now()
calcTime := end.Nanosecond() - start.Nanosecond() calcTime := end.Nanosecond() - start.Nanosecond()
result.CalcTime = zxcvbn_math.Round(float64(calcTime)*time.Nanosecond.Seconds(), .5, 3) result.CalcTime = zxcvbnmath.Round(float64(calcTime)*time.Nanosecond.Seconds(), .5, 3)
return result return result
} }

View File

@ -2,10 +2,7 @@ package zxcvbn
import ( import (
"testing" "testing"
"fmt"
"math" "math"
"strconv"
) )
/** /**
@ -16,16 +13,6 @@ const (
allowableError = float64(0.05) allowableError = float64(0.05)
) )
type failedTest struct {
Password string
Expect float64
Actual float64
PError float64
}
var failedTests []failedTest
var numTestRan int
func TestPasswordStrength(t *testing.T) { func TestPasswordStrength(t *testing.T) {
// Expected calculated by running zxcvbn-python // Expected calculated by running zxcvbn-python
@ -65,27 +52,21 @@ func TestPasswordStrength(t *testing.T) {
runTest(t, "eheuczkqyq", float64(42.813)) runTest(t, "eheuczkqyq", float64(42.813))
runTest(t, "rWibMFACxAUGZmxhVncy", float64(104.551)) runTest(t, "rWibMFACxAUGZmxhVncy", float64(104.551))
runTest(t, "Ba9ZyWABu99[BK#6MBgbH88Tofv)vs$", float64(161.278)) runTest(t, "Ba9ZyWABu99[BK#6MBgbH88Tofv)vs$", float64(161.278))
formatString := "%s : error should be less than %.2f \t Acctual error was: %.4f \t Expected entropy %.4f \t Actual entropy %.4f \n"
for _, test := range failedTests {
fmt.Printf(formatString, test.Password, allowableError, test.PError, test.Expect, test.Actual)
}
pTestPassed := (float64(numTestRan-len(failedTests)) / float64(numTestRan)) * float64(100)
fmt.Println("\n % of the test passed " + strconv.FormatFloat(pTestPassed, 'f', -1, 64))
} }
var formatString = "%s : error should be less than %.2f Acctual error: %.4f Expected entropy %.4f Actual entropy %.4f \n"
func runTest(t *testing.T, password string, pythonEntropy float64) { func runTest(t *testing.T, password string, pythonEntropy float64) {
//Calculated by running it through python-zxcvbn
goEntropy := GoPasswordStrength(password, nil) goEntropy := GoPasswordStrength(password, nil)
perror := math.Abs(goEntropy-pythonEntropy) / pythonEntropy perror := math.Abs(goEntropy-pythonEntropy) / pythonEntropy
numTestRan++ if perror > allowableError {
if perror > allowableError { t.Logf(formatString, password, allowableError, perror, pythonEntropy, goEntropy )
failedTests = append(failedTests, failedTest{Password: password, Expect: pythonEntropy, Actual: goEntropy, PError: perror})
} // t.Fail()
}
} }
func GoPasswordStrength(password string, userInputs []string) float64 { func GoPasswordStrength(password string, userInputs []string) float64 {