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 }
func getWordsEntropy(numWords int, guessesPerSec float64) Entropy { entropy := Entropy{} var ( avgHumanLifespanInYears float64 = 67.2 // https://en.wikipedia.org/wiki/Life_expectancy ageOfUniverseInYears float64 = 13798000000. // https://en.wikipedia.org/wiki/Age_of_the_universe ) entropy.Entropy = 12.92 * float64(numWords) // https://xkcd.com/936/ // https://security.stackexchange.com/questions/62832/is-the-oft-cited-xkcd-scheme-no-longer-good-advice/62881#62881 // https://hashcat.net/forum/thread-2580.html entropy.KeySpace = math.Pow(7776., float64(numWords)) // Divide the keySpace in half. On average it is expected that an // exhaustive search of only half the keySpace will result in success. entropy.HalfKeySpace = entropy.KeySpace / 2 // // "Assume that your adversary is capable of a trillion guesses per second" - Snowden // // http://www.nytimes.com/2013/08/18/magazine/laura-poitras-snowden.html?pagewanted=all&_r=0 // guessesPerSec = 1000000000000. entropy.Seconds = entropy.HalfKeySpace / guessesPerSec entropy.Minutes = entropy.Seconds / 60 entropy.Hours = entropy.Minutes / 60 entropy.Days = entropy.Hours / 24 entropy.Years = entropy.Days / 365 entropy.Millenia = entropy.Years / 1000 entropy.HumanLifetimes = entropy.Years / avgHumanLifespanInYears entropy.UniverseLifetimes = entropy.Years / ageOfUniverseInYears return entropy }
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 }
/* 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) calculatedEntropy := match.Entropy match.Entropy = calculatedEntropy candidateEntropy := upTo + calculatedEntropy 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 entropyToCrackTime(entropy float64) float64 { crackTime := (0.5 * math.Pow(float64(2), entropy)) * SECONDS_PER_GUESS return crackTime }