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  }