1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
package scoring
import (
"fmt"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
"github.com/nbutton23/zxcvbn-go/utils/math"
"math"
"sort"
)
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.
//(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
//several orders of magnitude!
SINGLE_GUESS float64 = 0.010
NUM_ATTACKERS float64 = 100 //Cores used to make guesses
SECONDS_PER_GUESS float64 = SINGLE_GUESS / NUM_ATTACKERS
)
type MinEntropyMatch struct {
Password string
Entropy float64
MatchSequence []match.Match
CrackTime float64
CrackTimeDisplay string
Score int
CalcTime float64
}
/*
Returns minimum entropy
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.
*/
func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntropyMatch {
bruteforceCardinality := float64(entropy.CalcBruteForceCardinality(password))
upToK := make([]float64, len(password))
backPointers := make([]match.Match, len(password))
for k := 0; k < len(password); k++ {
upToK[k] = get(upToK, k-1) + math.Log2(bruteforceCardinality)
for _, match := range matches {
if match.J != k {
continue
}
i, j := match.I, match.J
//see if best entropy up to i-1 + entropy of match is less that current min at j
upTo := get(upToK, i-1)
candidateEntropy := upTo + match.Entropy
if candidateEntropy < upToK[j] {
upToK[j] = candidateEntropy
match.Entropy = candidateEntropy
backPointers[j] = match
}
}
}
//walk backwards and decode the best sequence
var matchSequence []match.Match
passwordLen := len(password)
passwordLen--
for k := passwordLen; k >= 0; {
match := backPointers[k]
if match.Pattern != "" {
matchSequence = append(matchSequence, match)
k = match.I - 1
} else {
k--
}
}
sort.Sort(match.Matches(matchSequence))
makeBruteForceMatch := func(i, j int) match.Match {
return match.Match{Pattern: "bruteforce",
I: i,
J: j,
Token: password[i : j+1],
Entropy: math.Log2(math.Pow(bruteforceCardinality, float64(j-i)))}
}
k := 0
var matchSequenceCopy []match.Match
for _, match := range matchSequence {
i, j := match.I, match.J
if i-k > 0 {
matchSequenceCopy = append(matchSequenceCopy, makeBruteForceMatch(k, i-1))
}
k = j + 1
matchSequenceCopy = append(matchSequenceCopy, match)
}
if k < len(password) {
matchSequenceCopy = append(matchSequenceCopy, makeBruteForceMatch(k, len(password)-1))
}
var minEntropy float64
if len(password) == 0 {
minEntropy = float64(0)
} else {
minEntropy = upToK[len(password)-1]
}
crackTime := roundToXDigits(entropyToCrackTime(minEntropy), 3)
return MinEntropyMatch{Password: password,
Entropy: roundToXDigits(minEntropy, 3),
MatchSequence: matchSequenceCopy,
CrackTime: crackTime,
CrackTimeDisplay: displayTime(crackTime),
Score: crackTimeToScore(crackTime)}
}
func get(a []float64, i int) float64 {
if i < 0 || i >= len(a) {
return float64(0)
}
return a[i]
}
func entropyToCrackTime(entropy float64) float64 {
crackTime := (0.5 * math.Pow(float64(2), entropy)) * SECONDS_PER_GUESS
return crackTime
}
func roundToXDigits(number float64, digits int) float64 {
return zxcvbn_math.Round(number, .5, digits)
}
func displayTime(seconds float64) string {
formater := "%.1f %s"
minute := float64(60)
hour := minute * float64(60)
day := hour * float64(24)
month := day * float64(31)
year := month * float64(12)
century := year * float64(100)
if seconds < minute {
return "instant"
} else if seconds < hour {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/minute)), "minutes")
} else if seconds < day {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/hour)), "hours")
} else if seconds < month {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/day)), "days")
} else if seconds < year {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/month)), "months")
} else if seconds < century {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/century)), "years")
} else {
return "centuries"
}
}
func crackTimeToScore(seconds float64) int {
if seconds < math.Pow(10, 2) {
return 0
} else if seconds < math.Pow(10, 4) {
return 1
} else if seconds < math.Pow(10, 6) {
return 2
} else if seconds < math.Pow(10, 8) {
return 3
}
return 4
}
|