| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package entropy
- import (
- "github.com/nbutton23/zxcvbn-go/adjacency"
- "github.com/nbutton23/zxcvbn-go/match"
- "github.com/nbutton23/zxcvbn-go/utils/math"
- "math"
- "regexp"
- "unicode"
- )
- const (
- START_UPPER string = `^[A-Z][^A-Z]+$`
- END_UPPER string = `^[^A-Z]+[A-Z]$'`
- ALL_UPPER string = `^[A-Z]+$`
- NUM_YEARS = float64(119) // years match against 1900 - 2019
- NUM_MONTHS = float64(12)
- NUM_DAYS = float64(31)
- )
- var (
- KEYPAD_STARTING_POSITIONS = len(adjacency.AdjacencyGph["keypad"].Graph)
- KEYPAD_AVG_DEGREE = adjacency.AdjacencyGph["keypad"].CalculateAvgDegree()
- )
- func DictionaryEntropy(match match.Match, rank float64) float64 {
- baseEntropy := math.Log2(rank)
- upperCaseEntropy := extraUpperCaseEntropy(match)
- //TODO: L33t
- return baseEntropy + upperCaseEntropy
- }
- func extraUpperCaseEntropy(match match.Match) float64 {
- word := match.Token
- allLower := true
- for _, char := range word {
- if unicode.IsUpper(char) {
- allLower = false
- break
- }
- }
- if allLower {
- return float64(0)
- }
- //a capitalized word is the most common capitalization scheme,
- //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.
- for _, regex := range []string{START_UPPER, END_UPPER, ALL_UPPER} {
- matcher := regexp.MustCompile(regex)
- if matcher.MatchString(word) {
- return float64(1)
- }
- }
- //Otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or
- //less. Or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters
- //with L lowercase letters or less.
- countUpper, countLower := float64(0), float64(0)
- for _, char := range word {
- if unicode.IsUpper(char) {
- countUpper++
- } else if unicode.IsLower(char) {
- countLower++
- }
- }
- totalLenght := countLower + countUpper
- var possibililities float64
- for i := float64(0); i <= math.Min(countUpper, countLower); i++ {
- possibililities += float64(zxcvbn_math.NChoseK(totalLenght, i))
- }
- if possibililities < 1 {
- return float64(1)
- }
- return float64(math.Log2(possibililities))
- }
- func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
- var s, d float64
- if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" {
- //todo: verify qwerty and dvorak have the same length and degree
- s = float64(len(adjacency.BuildQwerty().Graph))
- d = adjacency.BuildQwerty().CalculateAvgDegree()
- } else {
- s = float64(KEYPAD_STARTING_POSITIONS)
- d = KEYPAD_AVG_DEGREE
- }
- possibilities := float64(0)
- length := float64(len(match.Token))
- //TODO: Should this be <= or just < ?
- //Estimate the number of possible patterns w/ length L or less with t turns or less
- for i := float64(2); i <= length+1; i++ {
- possibleTurns := math.Min(float64(turns), i-1)
- for j := float64(1); j <= possibleTurns+1; j++ {
- x := zxcvbn_math.NChoseK(i-1, j-1) * s * math.Pow(d, j)
- possibilities += x
- }
- }
- entropy := math.Log2(possibilities)
- //add extra entropu for shifted keys. ( % instead of 5 A instead of a)
- //Math is similar to extra entropy for uppercase letters in dictionary matches.
- if S := float64(shiftCount); S > float64(0) {
- possibilities = float64(0)
- U := length - S
- for i := float64(0); i < math.Min(S, U)+1; i++ {
- possibilities += zxcvbn_math.NChoseK(S+U, i)
- }
- entropy += math.Log2(possibilities)
- }
- return entropy
- }
- func RepeatEntropy(match match.Match) float64 {
- cardinality := CalcBruteForceCardinality(match.Token)
- entropy := math.Log2(cardinality * float64(len(match.Token)))
- return entropy
- }
- //TODO: Validate against python
- func CalcBruteForceCardinality(password string) float64 {
- lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0)
- for _, char := range password {
- if unicode.IsLower(char) {
- lower = float64(26)
- } else if unicode.IsDigit(char) {
- digits = float64(10)
- } else if unicode.IsUpper(char) {
- upper = float64(26)
- } else {
- symbols = float64(33)
- }
- }
- cardinality := lower + upper + digits + symbols
- return cardinality
- }
- func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 {
- firstChar := match.Token[0]
- baseEntropy := float64(0)
- if string(firstChar) == "a" || string(firstChar) == "1" {
- baseEntropy = float64(0)
- } else {
- baseEntropy = math.Log2(float64(dictionaryLength))
- //TODO: should this be just the first or any char?
- if unicode.IsUpper(rune(firstChar)) {
- baseEntropy++
- }
- }
- if !ascending {
- baseEntropy++
- }
- return baseEntropy + math.Log2(float64(len(match.Token)))
- }
- func ExtraLeetEntropy(match match.Match, password string) float64 {
- var subsitutions float64
- var unsub float64
- subPassword := password[match.I:match.J]
- for index, char := range subPassword {
- if string(char) != string(match.Token[index]) {
- subsitutions++
- } else {
- //TODO: Make this only true for 1337 chars that are not subs?
- unsub++
- }
- }
- var possibilities float64
- for i := float64(0); i <= math.Min(subsitutions, unsub)+1; i++ {
- possibilities += zxcvbn_math.NChoseK(subsitutions+unsub, i)
- }
- if possibilities <= 1 {
- return float64(1)
- }
- return math.Log2(possibilities)
- }
- func YearEntropy(dateMatch match.DateMatch) float64 {
- return math.Log2(NUM_YEARS)
- }
- func DateEntropy(dateMatch match.DateMatch) float64 {
- var entropy float64
- if dateMatch.Year < 100 {
- entropy = math.Log2(NUM_DAYS * NUM_MONTHS * 100)
- } else {
- entropy = math.Log2(NUM_DAYS * NUM_MONTHS * NUM_YEARS)
- }
- if dateMatch.Separator != "" {
- entropy += 2 //add two bits for separator selection [/,-,.,etc]
- }
- return entropy
- }
|