private void GetMatchFeedback(Match match, bool isSoleMatch, PasswordMetricResult result) { switch (match.Pattern) { case "dictionary": GetDictionaryMatchFeedback((DictionaryMatch)match, isSoleMatch, result); break; case "spatial": SpatialMatch spatialMatch = (SpatialMatch)match; if (spatialMatch.Turns == 1) { result.warning = Warning.StraightRow; } else { result.warning = Warning.ShortKeyboardPatterns; } result.suggestions.Clear(); result.suggestions.Add(Suggestion.UseLongerKeyboardPattern); break; case "repeat": //todo: add support for repeated sequences longer than 1 char // if(match.Token.Length == 1) result.warning = Warning.RepeatsLikeAaaEasy; // else // result.warning = Warning.RepeatsLikeAbcSlighterHarder; result.suggestions.Clear(); result.suggestions.Add(Suggestion.AvoidRepeatedWordsAndChars); break; case "sequence": result.warning = Warning.SequenceAbcEasy; result.suggestions.Clear(); result.suggestions.Add(Suggestion.AvoidSequences); break; //todo: add support for recent_year, however not example exist on https://dl.dropboxusercontent.com/u/209/zxcvbn/test/index.html case "date": result.warning = Warning.DatesEasy; result.suggestions.Clear(); result.suggestions.Add(Suggestion.AvoidDatesYearsAssociatedYou); break; } }
/// <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>A result object for the lowest entropy match sequence</returns> private PasswordMetricResult FindMinimumEntropyMatch(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.End == k)) { var candidate_entropy = (match.Begin <= 0 ? 0 : minimumEntropyToIndex[match.Begin - 1]) + match.Entropy; if (candidate_entropy < minimumEntropyToIndex[k]) { minimumEntropyToIndex[k] = candidate_entropy; bestMatchForIndex[k] = match; } } } // Walk backwards through lowest entropy matches, to build the best password sequence var matchSequence = new List <Match>(); for (var k = password.Length - 1; k >= 0; k--) { if (bestMatchForIndex[k] != null) { matchSequence.Add(bestMatchForIndex[k]); k = bestMatchForIndex[k].Begin; // Jump back to start of match } } matchSequence.Reverse(); // The match sequence might have gaps, fill in with bruteforce matching // After this the matches in matchSequence must cover the whole string (i.e. match[k].j == match[k + 1].i - 1) if (matchSequence.Count == 0) { // To make things easy, we'll separate out the case where there are no matches so everything is bruteforced matchSequence.Add(new Match() { Begin = 0, End = password.Length, Token = password, Cardinality = bruteforce_cardinality, Pattern = BruteforcePattern, Entropy = Math.Log(Math.Pow(bruteforce_cardinality, password.Length), 2) }); } else { // There are matches, so find the gaps and fill them in var matchSequenceCopy = new List <Match>(); for (var k = 0; k < matchSequence.Count; k++) { var m1 = matchSequence[k]; var m2 = (k < matchSequence.Count - 1 ? matchSequence[k + 1] : new Match() { Begin = password.Length }); // Next match, or a match past the end of the password matchSequenceCopy.Add(m1); if (m1.End < m2.Begin - 1) { // Fill in gap var ns = m1.End + 1; var ne = m2.Begin - 1; matchSequenceCopy.Add(new Match() { Begin = ns, End = ne, Token = password.Substring(ns, ne - ns + 1), Cardinality = bruteforce_cardinality, Pattern = BruteforcePattern, Entropy = Math.Log(Math.Pow(bruteforce_cardinality, ne - ns + 1), 2) }); } } matchSequence = matchSequenceCopy; } var minEntropy = (password.Length == 0 ? 0 : minimumEntropyToIndex[password.Length - 1]); var crackTime = PasswordScoring.EntropyToCrackTime(minEntropy); var result = new PasswordMetricResult(); result.Password = password; result.Entropy = Math.Round(minEntropy, 3); result.MatchSequence = matchSequence; result.CrackTime = Math.Round(crackTime, 3); result.CrackTimeDisplay = Utility.DisplayTime(crackTime, this.translation); result.Score = PasswordScoring.CrackTimeToScore(crackTime); //starting feedback if ((matchSequence == null) || (matchSequence.Count() == 0)) { result.warning = Warning.Default; result.suggestions.Clear(); result.suggestions.Add(Suggestion.Default); } else { //no Feedback if score is good or great if (result.Score > 2) { result.warning = Warning.Empty; result.suggestions.Clear(); result.suggestions.Add(Suggestion.Empty); } else { //tie feedback to the longest match for longer sequences Match longestMatch = GetLongestMatch(matchSequence); GetMatchFeedback(longestMatch, matchSequence.Count() == 1, result); result.suggestions.Insert(0, Suggestion.AddAnotherWordOrTwo); } } return(result); }
private void GetDictionaryMatchFeedback(DictionaryMatch match, bool isSoleMatch, PasswordMetricResult result) { if (match.DictionaryName.Equals("passwords")) { //todo: add support for reversed words if (isSoleMatch == true && !(match is L33tDictionaryMatch)) { if (match.Rank <= 10) { result.warning = Warning.Top10Passwords; } else if (match.Rank <= 100) { result.warning = Warning.Top100Passwords; } else { result.warning = Warning.CommonPasswords; } } else if (PasswordScoring.CrackTimeToScore(PasswordScoring.EntropyToCrackTime(match.Entropy)) <= 1) { result.warning = Warning.SimilarCommonPasswords; } } else if (match.DictionaryName.Equals("english")) { if (isSoleMatch == true) { result.warning = Warning.WordEasy; } } else if (match.DictionaryName.Equals("surnames") || match.DictionaryName.Equals("male_names") || match.DictionaryName.Equals("female_names")) { if (isSoleMatch == true) { result.warning = Warning.NameSurnamesEasy; } else { result.warning = Warning.CommonNameSurnamesEasy; } } else { result.warning = Warning.Empty; } string word = match.Token; if (Regex.IsMatch(word, PasswordScoring.StartUpper)) { result.suggestions.Add(Suggestion.CapsDontHelp); } else if (Regex.IsMatch(word, PasswordScoring.AllUpper) && !word.Equals(word.ToLowerInvariant())) { result.suggestions.Add(Suggestion.AllCapsEasy); } //todo: add support for reversed words //if match.reversed and match.token.length >= 4 // suggestions.push "Reversed words aren't much harder to guess" if (match is L33tDictionaryMatch) { result.suggestions.Add(Suggestion.PredictableSubstitutionsEasy); } }