Fix some small preformance issues as well as get lint passing
This commit is contained in:
parent
780cc39d5c
commit
953e4e2086
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -82,7 +94,7 @@ func (adjGrp AdjacencyGraph) CalculateAvgDegree() float64 {
|
||||||
for _, value := range adjGrp.Graph {
|
for _, value := range adjGrp.Graph {
|
||||||
|
|
||||||
for _, char := range value {
|
for _, char := range value {
|
||||||
if char != "" || char != " " {
|
if len(char) != 0 || char != " " {
|
||||||
avg += float64(len(char))
|
avg += float64(len(char))
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 != "" {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,19 +18,21 @@ func buildDictMatcher(dictName string, rankedDict map[string]int) func(password
|
||||||
}
|
}
|
||||||
|
|
||||||
func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match {
|
func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match {
|
||||||
length := len(password)
|
|
||||||
var results []match.Match
|
var results []match.Match
|
||||||
pwLower := strings.ToLower(password)
|
pwLower := strings.ToLower(password)
|
||||||
|
|
||||||
|
pwLowerRunes := []rune(pwLower)
|
||||||
|
length := len(pwLowerRunes)
|
||||||
|
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
for j := i; j < length; j++ {
|
for j := i; j < length; j++ {
|
||||||
word := pwLower[i : j+1]
|
word := pwLowerRunes[i : j+1]
|
||||||
if val, ok := rankedDict[word]; ok {
|
if val, ok := rankedDict[string(word)]; ok {
|
||||||
matchDic := match.Match{Pattern: "dictionary",
|
matchDic := match.Match{Pattern: "dictionary",
|
||||||
DictionaryName: dictionaryName,
|
DictionaryName: dictionaryName,
|
||||||
I: i,
|
I: i,
|
||||||
J: j,
|
J: j,
|
||||||
Token: password[i : j+1],
|
Token: string([]rune(password)[i : j+1]),
|
||||||
}
|
}
|
||||||
matchDic.Entropy = entropy.DictionaryEntropy(matchDic, float64(val))
|
matchDic.Entropy = entropy.DictionaryEntropy(matchDic, float64(val))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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++
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package zxcvbn_math
|
package zxcvbnmath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,26 +52,20 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue