gitee.com/lonely0422/gometalinter.git@v3.0.1-0.20190307123442-32416ab75314+incompatible/_linters/src/github.com/nbutton23/zxcvbn-go/entropy/entropyCalculator.go (about) 1 package entropy 2 3 import ( 4 "github.com/nbutton23/zxcvbn-go/adjacency" 5 "github.com/nbutton23/zxcvbn-go/match" 6 "github.com/nbutton23/zxcvbn-go/utils/math" 7 "math" 8 "regexp" 9 "unicode" 10 ) 11 12 const ( 13 START_UPPER string = `^[A-Z][^A-Z]+$` 14 END_UPPER string = `^[^A-Z]+[A-Z]$'` 15 ALL_UPPER string = `^[A-Z]+$` 16 NUM_YEARS = float64(119) // years match against 1900 - 2019 17 NUM_MONTHS = float64(12) 18 NUM_DAYS = float64(31) 19 ) 20 21 var ( 22 KEYPAD_STARTING_POSITIONS = len(adjacency.AdjacencyGph["keypad"].Graph) 23 KEYPAD_AVG_DEGREE = adjacency.AdjacencyGph["keypad"].CalculateAvgDegree() 24 ) 25 26 func DictionaryEntropy(match match.Match, rank float64) float64 { 27 baseEntropy := math.Log2(rank) 28 upperCaseEntropy := extraUpperCaseEntropy(match) 29 //TODO: L33t 30 return baseEntropy + upperCaseEntropy 31 } 32 33 func extraUpperCaseEntropy(match match.Match) float64 { 34 word := match.Token 35 36 allLower := true 37 38 for _, char := range word { 39 if unicode.IsUpper(char) { 40 allLower = false 41 break 42 } 43 } 44 if allLower { 45 return float64(0) 46 } 47 48 //a capitalized word is the most common capitalization scheme, 49 //so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy. 50 //allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe. 51 52 for _, regex := range []string{START_UPPER, END_UPPER, ALL_UPPER} { 53 matcher := regexp.MustCompile(regex) 54 55 if matcher.MatchString(word) { 56 return float64(1) 57 } 58 } 59 //Otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or 60 //less. Or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters 61 //with L lowercase letters or less. 62 63 countUpper, countLower := float64(0), float64(0) 64 for _, char := range word { 65 if unicode.IsUpper(char) { 66 countUpper++ 67 } else if unicode.IsLower(char) { 68 countLower++ 69 } 70 } 71 totalLenght := countLower + countUpper 72 var possibililities float64 73 74 for i := float64(0); i <= math.Min(countUpper, countLower); i++ { 75 possibililities += float64(zxcvbn_math.NChoseK(totalLenght, i)) 76 } 77 78 if possibililities < 1 { 79 return float64(1) 80 } 81 82 return float64(math.Log2(possibililities)) 83 } 84 85 func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 { 86 var s, d float64 87 if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" { 88 //todo: verify qwerty and dvorak have the same length and degree 89 s = float64(len(adjacency.BuildQwerty().Graph)) 90 d = adjacency.BuildQwerty().CalculateAvgDegree() 91 } else { 92 s = float64(KEYPAD_STARTING_POSITIONS) 93 d = KEYPAD_AVG_DEGREE 94 } 95 96 possibilities := float64(0) 97 98 length := float64(len(match.Token)) 99 100 //TODO: Should this be <= or just < ? 101 //Estimate the number of possible patterns w/ length L or less with t turns or less 102 for i := float64(2); i <= length+1; i++ { 103 possibleTurns := math.Min(float64(turns), i-1) 104 for j := float64(1); j <= possibleTurns+1; j++ { 105 x := zxcvbn_math.NChoseK(i-1, j-1) * s * math.Pow(d, j) 106 possibilities += x 107 } 108 } 109 110 entropy := math.Log2(possibilities) 111 //add extra entropu for shifted keys. ( % instead of 5 A instead of a) 112 //Math is similar to extra entropy for uppercase letters in dictionary matches. 113 114 if S := float64(shiftCount); S > float64(0) { 115 possibilities = float64(0) 116 U := length - S 117 118 for i := float64(0); i < math.Min(S, U)+1; i++ { 119 possibilities += zxcvbn_math.NChoseK(S+U, i) 120 } 121 122 entropy += math.Log2(possibilities) 123 } 124 125 return entropy 126 } 127 128 func RepeatEntropy(match match.Match) float64 { 129 cardinality := CalcBruteForceCardinality(match.Token) 130 entropy := math.Log2(cardinality * float64(len(match.Token))) 131 132 return entropy 133 } 134 135 //TODO: Validate against python 136 func CalcBruteForceCardinality(password string) float64 { 137 lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0) 138 139 for _, char := range password { 140 if unicode.IsLower(char) { 141 lower = float64(26) 142 } else if unicode.IsDigit(char) { 143 digits = float64(10) 144 } else if unicode.IsUpper(char) { 145 upper = float64(26) 146 } else { 147 symbols = float64(33) 148 } 149 } 150 151 cardinality := lower + upper + digits + symbols 152 return cardinality 153 } 154 155 func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 { 156 firstChar := match.Token[0] 157 baseEntropy := float64(0) 158 if string(firstChar) == "a" || string(firstChar) == "1" { 159 baseEntropy = float64(0) 160 } else { 161 baseEntropy = math.Log2(float64(dictionaryLength)) 162 //TODO: should this be just the first or any char? 163 if unicode.IsUpper(rune(firstChar)) { 164 baseEntropy++ 165 } 166 } 167 168 if !ascending { 169 baseEntropy++ 170 } 171 return baseEntropy + math.Log2(float64(len(match.Token))) 172 } 173 174 func ExtraLeetEntropy(match match.Match, password string) float64 { 175 var subsitutions float64 176 var unsub float64 177 subPassword := password[match.I:match.J] 178 for index, char := range subPassword { 179 if string(char) != string(match.Token[index]) { 180 subsitutions++ 181 } else { 182 //TODO: Make this only true for 1337 chars that are not subs? 183 unsub++ 184 } 185 } 186 187 var possibilities float64 188 189 for i := float64(0); i <= math.Min(subsitutions, unsub)+1; i++ { 190 possibilities += zxcvbn_math.NChoseK(subsitutions+unsub, i) 191 } 192 193 if possibilities <= 1 { 194 return float64(1) 195 } 196 return math.Log2(possibilities) 197 } 198 199 func YearEntropy(dateMatch match.DateMatch) float64 { 200 return math.Log2(NUM_YEARS) 201 } 202 203 func DateEntropy(dateMatch match.DateMatch) float64 { 204 var entropy float64 205 if dateMatch.Year < 100 { 206 entropy = math.Log2(NUM_DAYS * NUM_MONTHS * 100) 207 } else { 208 entropy = math.Log2(NUM_DAYS * NUM_MONTHS * NUM_YEARS) 209 } 210 211 if dateMatch.Separator != "" { 212 entropy += 2 //add two bits for separator selection [/,-,.,etc] 213 } 214 return entropy 215 }