private static double EstimateGuesses(ZxcvbnMatch match, string password) { if (match.Guesses.HasValue) { return(match.Guesses.Value); } int minGuesses = 1; if (match.Token.Length < password.Length) { minGuesses = match.Token.Length == 1 ? minSubmatchGuessesSingleChar : minSubmatchGuessesMultiChar; } double guesses = match.Pattern switch { MatchPattern.Dictionary => DictionaryGuesses(match), MatchPattern.Spatial => SpatialGuesses(match), MatchPattern.Repeat => RepeatGuesses(match), MatchPattern.Sequence => SequenceGuesses(match), MatchPattern.Regex => RegexGuesses(match), MatchPattern.Date => DateGuesses(match), _ => BruteforceGuesses(match) }; match.Guesses = Math.Max(guesses, minGuesses); match.GuessesLog10 = Log10(match.Guesses.Value); return(match.Guesses.Value); }
private static double RegexGuesses(ZxcvbnMatch match) { return(match.RegexName switch { RegexName.RecentYear => Math.Max(Math.Abs(int.Parse(match.RegexMatch.Value) - referenceYear), minYearSpace), RegexName.None => 0, _ => Math.Pow((int)match.RegexName, match.Token.Length) });
private static double SequenceGuesses(ZxcvbnMatch match) { char firstChar = match.Token[0]; double baseGuesses = obviousStartingPoints.Contains(match.Token[0]) ? 4 : (char.IsDigit(firstChar) ? 10 : 26); if (!match.Ascending) { baseGuesses *= 2; } return(baseGuesses * match.Token.Length); }
private static Feedback GetMatchFeedback(ZxcvbnMatch match, bool isSoleMatch) { return(match.Pattern switch { MatchPattern.Dictionary => GetDictionaryMatchFeedback(match, isSoleMatch), MatchPattern.Spatial => new Feedback(match.Turns == 1 ? "Straight rows of keys are easy to guess" : "Short keyboard patterns are easy to guess", "Use a longer keyboard pattern with more turns"), MatchPattern.Repeat => new Feedback(match.BaseToken.Length == 1 ? @"Repeats like ""aaa"" are easy to guess" : @"Repeats like ""abcabcabc"" are only slightly harder to guess than ""abc""", "Avoid repeated words and characters"), MatchPattern.Sequence => new Feedback("Sequences like abc or 6543 are easy to guess", "Avoid sequences"), MatchPattern.Regex => match.RegexName == RegexName.RecentYear ? new Feedback("Recent years are easy to guess", "Avoid recent years", "Avoid years that are associated with you") : Default, MatchPattern.Date => new Feedback("Dates are often easy to guess", "Avoid dates and years that are associated with you"), _ => Empty });
private static double BruteforceGuesses(ZxcvbnMatch match) { double guesses = Math.Pow(bruteforceCardinality, match.Token.Length); if (double.IsInfinity(guesses)) { guesses = double.MaxValue; } double minGuesses = match.Token.Length == 1 ? minSubmatchGuessesSingleChar : minSubmatchGuessesMultiChar; minGuesses += 1; return(Math.Max(guesses, minGuesses)); }
internal static Feedback GetFeedback(double score, ZxcvbnMatch[] sequence) { if (sequence == null || sequence.Length == 0) { return(Default); } if (score > 2) { return(Empty); } ZxcvbnMatch longestMatch = sequence.OrderByDescending(m => m.Token.Length).First(); Feedback feedback = GetMatchFeedback(longestMatch, sequence.Length == 1); feedback.Suggestions = feedback.Suggestions.Concat(new[] { "Add another word or two. Uncommon words are better." }).ToArray(); return(feedback); }
public static ZxcvbnResult MostGuessableMatchSequence(string password, IEnumerable <ZxcvbnMatch> matches, bool excludeAdditive = false) { List <ZxcvbnMatch>[] matchesByJ = Enumerable.Range(0, password.Length).Select(i => new List <ZxcvbnMatch>()).ToArray(); for (int i = 0; i < password.Length; i++) { matchesByJ[i] = new List <ZxcvbnMatch>(); } foreach (ZxcvbnMatch m in matches) { matchesByJ[m.J].Add(m); } foreach (List <ZxcvbnMatch> lst in matchesByJ) { lst.Sort((m1, m2) => m1.I - m2.I); } Dictionary <int, ZxcvbnMatch>[] optimalM = Enumerable.Range(0, password.Length).Select(i => new Dictionary <int, ZxcvbnMatch>()).ToArray(); Dictionary <int, double>[] optimalPi = Enumerable.Range(0, password.Length).Select(i => new Dictionary <int, double>()).ToArray(); Dictionary <int, double>[] optimalG = Enumerable.Range(0, password.Length).Select(i => new Dictionary <int, double>()).ToArray(); void Update(ZxcvbnMatch m, int l) { int k = m.J; double pi = EstimateGuesses(m, password); if (l > 1) { pi *= optimalPi[m.I - 1][l - 1]; } double g = Factorial(l) * pi; if (!excludeAdditive) { g += Math.Pow(minGuessesBeforeGrowingSequence, l - 1); } foreach (KeyValuePair <int, double> kv in optimalG[k]) { int competingL = kv.Key; double competingG = kv.Value; if (competingL > l) { continue; } else if (competingG <= g) { return; } } optimalG[k].AddOrSet(l, g); optimalM[k].AddOrSet(l, m); optimalPi[k].AddOrSet(l, pi); } void BruteforceUpdate(int k) { ZxcvbnMatch m = MakeBruteforceMatch(0, k); Update(m, 1); for (int i = 1; i <= k; i++) { m = MakeBruteforceMatch(i, k); foreach (KeyValuePair <int, ZxcvbnMatch> kv in optimalM[i - 1]) { int l = kv.Key; ZxcvbnMatch lastM = kv.Value; if (lastM.Pattern == MatchPattern.Bruteforce) { continue; } Update(m, l + 1); } } } ZxcvbnMatch MakeBruteforceMatch(int i, int j) => new ZxcvbnMatch { Pattern = MatchPattern.Bruteforce, Token = password.Substring(i, j - i + 1), I = i, J = j }; ZxcvbnMatch[] Unwind(int n) { List <ZxcvbnMatch> optimalMatchSequence = new List <ZxcvbnMatch>(); int k = n - 1; int l = 0; double g = double.PositiveInfinity; foreach (KeyValuePair <int, double> kv in optimalG[k]) { int candidateL = kv.Key; double candidateG = kv.Value; if (candidateG < g) { l = candidateL; g = candidateG; } } while (k >= 0) { ZxcvbnMatch m = optimalM[k][l]; optimalMatchSequence.Insert(0, m); k = m.I - 1; l--; } return(optimalMatchSequence.ToArray()); } for (int k = 0; k < password.Length; k++) { foreach (ZxcvbnMatch m in matchesByJ[k]) { if (m.I > 0) { foreach (int l in optimalM[m.I - 1].Keys.OrderBy(k => k)) { Update(m, l + 1); } } else { Update(m, 1); } } BruteforceUpdate(k); } ZxcvbnMatch[] optimalMatchSequence = Unwind(password.Length); int optimalL = optimalMatchSequence.Length; double guesses = password.Length == 0 ? 1 : optimalG[password.Length - 1][optimalL]; return(new ZxcvbnResult { Password = password, Guesses = guesses, GuessesLog10 = Log10(guesses), Sequence = optimalMatchSequence }); }
private static double RepeatGuesses(ZxcvbnMatch match) { return(match.BaseGuesses * match.RepeatCount); }