/// <summary> /// Returns a new result structure initialised with data for the lowest entropy result of all of the matches passed in, adding brute-force /// matches where there are no lesser entropy found pattern matches. /// </summary> /// <param name="matches">Password being evaluated</param> /// <param name="password">List of matches found against the password</param> /// <returns>An estimation of the time required to crack the given password</returns> private static double CalculateCrackTime(string password, IEnumerable <Match> matches) { var bruteforce_cardinality = PasswordScoring.PasswordCardinality(password); // Minimum entropy up to position k in the password var minimumEntropyToIndex = new double[password.Length]; var bestMatchForIndex = new Match[password.Length]; for (var k = 0; k < password.Length; k++) { // Start with bruteforce scenario added to previous sequence to beat minimumEntropyToIndex[k] = (k == 0 ? 0 : minimumEntropyToIndex[k - 1]) + Math.Log(bruteforce_cardinality, 2); // All matches that end at the current character, test to see if the entropy is less foreach (var match in matches.Where(m => m.j == k)) { var candidate_entropy = (match.i <= 0 ? 0 : minimumEntropyToIndex[match.i - 1]) + match.Entropy; if (candidate_entropy < minimumEntropyToIndex[k]) { minimumEntropyToIndex[k] = candidate_entropy; bestMatchForIndex[k] = match; } } } var minEntropy = (password.Length == 0 ? 0 : minimumEntropyToIndex[password.Length - 1]); var crackTime = PasswordScoring.EntropyToCrackTime(minEntropy); return(Math.Round(crackTime, 3)); }
/// <summary> /// Estimate the extra entropy in a token that comes from mixing upper and lowercase letters. /// This has been moved to a static function so that it can be used in multiple entropy calculations. /// </summary> /// <param name="word">The word to calculate uppercase entropy for</param> /// <returns>An estimation of the entropy gained from casing in <paramref name="word"/></returns> internal static double CalculateUppercaseEntropy(string word) { const string StartUpper = "^[A-Z][^A-Z]+$"; const string EndUpper = "^[^A-Z]+[A-Z]$"; const string AllUpper = "^[^a-z]+$"; const string AllLower = "^[^A-Z]+$"; if (Regex.IsMatch(word, AllLower)) { return(0); } // If the word is all uppercase add's only one bit of entropy, add only one bit for initial/end single cap only if (new[] { StartUpper, EndUpper, AllUpper }.Any(re => Regex.IsMatch(word, re))) { return(1); } var lowers = word.Where(c => 'a' <= c && c <= 'z').Count(); var uppers = word.Where(c => 'A' <= c && c <= 'Z').Count(); // Calculate numer of ways to capitalise (or inverse if there are fewer lowercase chars) and return lg for entropy return(Math.Log(Enumerable.Range(0, Math.Min(uppers, lowers) + 1).Sum(i => PasswordScoring.Binomial(uppers + lowers, i)), 2)); }