Пример #1
0
        /// <summary>
        /// Perform the password matching on the given password and user inputs.
        /// </summary>
        /// <param name="password">The password to assess.</param>
        /// <param name="userInputs">Optionally, an enumarable of user data.</param>
        /// <returns>Result for the password.</returns>
        public static Result EvaluatePassword(string password, IEnumerable <string> userInputs = null)
        {
            password   = password ?? string.Empty;
            userInputs = userInputs ?? Enumerable.Empty <string>();

            var timer = System.Diagnostics.Stopwatch.StartNew();

            var matches = GetAllMatches(password, userInputs);
            var result  = PasswordScoring.MostGuessableMatchSequence(password, matches);

            timer.Stop();

            var attackTimes = TimeEstimates.EstimateAttackTimes(result.Guesses);
            var feedback    = Feedback.GetFeedback(result.Score, result.Sequence);

            var finalResult = new Result
            {
                CalcTime         = timer.ElapsedMilliseconds,
                CrackTime        = attackTimes.CrackTimesSeconds,
                CrackTimeDisplay = attackTimes.CrackTimesDisplay,
                Score            = attackTimes.Score,
                MatchSequence    = result.Sequence,
                Guesses          = result.Guesses,
                Password         = result.Password,
                Feedback         = feedback,
            };

            return(finalResult);
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        /// <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>
        public 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));
        }
Пример #5
0
        /// <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 Result 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.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;
                    }
                }
            }

            // 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].i; // 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()
                {
                    i           = 0,
                    j           = 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()
                    {
                        i = password.Length
                    });                                                                                                  // Next match, or a match past the end of the password

                    matchSequenceCopy.Add(m1);
                    if (m1.j < m2.i - 1)
                    {
                        // Fill in gap
                        var ns = m1.j + 1;
                        var ne = m2.i - 1;
                        matchSequenceCopy.Add(new Match()
                        {
                            i           = ns,
                            j           = 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 Result()
            {
                Password         = password,
                Entropy          = Math.Round(minEntropy, 3),
                MatchSequence    = matchSequence,
                CrackTime        = Math.Round(crackTime, 3),
                CrackTimeDisplay = Utility.DisplayTime(crackTime, translation),
                Score            = PasswordScoring.CrackTimeToScore(crackTime)
            };

            return(result);
        }
Пример #6
0
        public static Result MostGuessableMatchSequence(string password, IEnumerable <Match> matches, bool _exclude_additive = false)
        {
            int n = password.Length;

            List <List <Match> > matchesByJ = new List <List <Match> >();

            foreach (int i in EnumerableUtility.Range(0, n))
            {
                matchesByJ.Add(new List <Match>());
            }

            foreach (Match m in matches)
            {
                matchesByJ[m.j].Add(m);
            }

            foreach (List <Match> lst in matchesByJ)
            {
                lst.Sort(delegate(Match m1, Match m2)
                {
                    return(m1.i - m2.i);
                });
            }

            Optimal optimal = new Optimal(n);

            /*
             # helper: considers whether a length-l sequence ending at match m is better (fewer guesses)
             # than previously encountered sequences, updating state if so.
             */
            void update(Match m, int l)
            {
                // helper: considers whether a length-l sequence ending at match m is better (fewer guesses)
                // than previously encountered sequences, updating state if so.
                int    k  = m.j;
                double pi = EstimateGuesses(m, password);

                if (l > 1)
                {
                    /*
                     # we're considering a length-l sequence ending with match m:
                     # obtain the product term in the minimization function by multiplying m's guesses
                     # by the product of the length-(l-1) sequence ending just before m, at m.i - 1.
                     */
                    pi *= optimal.pi[m.i - 1][l - 1];
                }
                // calculate the minimization func
                double g = factorial(l) * pi;

                if (!_exclude_additive)
                {
                    g += Math.Pow(MIN_GUESSES_BEFORE_GROWING_SEQUENCE, l - 1);
                }
                // update state if new best
                // first see if any competing sequences covering this prefix, with l or fewer matches,
                // fare better than this sequence. if so, skip it and return
                foreach (KeyValuePair <int, double> val in optimal.g[k])
                {
                    int    competingL = val.Key;
                    double competingG = val.Value;
                    if (competingL > l)
                    {
                        continue;
                    }
                    if (competingG <= g)
                    {
                        return;
                    }
                }
                optimal.g[k][l]  = g;
                optimal.m[k][l]  = m;
                optimal.pi[k][l] = pi;
            }

            // helper: make bruteforce match objects spanning i to j, inclusive.
            Match makeBruteForceMatch(int i, int j)
            {
                Match m = new Match();

                m.Token   = password.Substring(i, j - i + 1);
                m.Pattern = "bruteforce";
                m.i       = i;
                m.j       = j;
                return(m);
            }

            // helper: evaluate bruteforce matches ending at k.
            void bruteForceUpdate(int k)
            {
                // see eif a single bruteforce match spanning the k-prefix is optimal
                Match m = makeBruteForceMatch(0, k);

                update(m, 1);
                foreach (int i in EnumerableUtility.Range(1, k + 1))
                {
                    // generate k bruteforce matches, spanning from (i=1, j=k) up to (i=k, j=k)
                    // see if adding these new matches to any of the sequences in optimal[i-1]
                    // leads to new bests
                    m = makeBruteForceMatch(i, k);
                    foreach (KeyValuePair <int, Match> val in optimal.m[i - 1])
                    {
                        int l = val.Key;
                        // corner: an optimal sequence will never have two adjacent bruteforce matches
                        // it is strictly better to have a single bruteforce match spanning the same region:
                        // same contribution to the guess product with a lower length
                        // --> safe to skip those cases
                        if (val.Value.Pattern != null && val.Value.Pattern.Equals("bruteforce"))
                        {
                            continue;
                        }
                        // try adding m to this length-l sequence
                        update(m, l + 1);
                    }
                }
            }

            // helper: step backwards through optimal.m starting at the end,
            // constructing the final optimal match sequence
            List <Match> unwind(int nN)
            {
                List <Match> optimalMatchSequenceList = new List <Match>();
                int          k = nN - 1;
                // find the final best seqence length and score
                int    l = 1;
                double g = Double.MaxValue;

                foreach (KeyValuePair <int, double> val in optimal.g[k])
                {
                    int    candidateL = val.Key;
                    double candidateG = val.Value;
                    if (candidateG < g)
                    {
                        l = candidateL;
                        g = candidateG;
                    }
                }

                while (k >= 0)
                {
                    Match m = optimal.m[k][l];
                    optimalMatchSequenceList.Insert(0, m);
                    k = m.i - 1;
                    l--;
                }
                return(optimalMatchSequenceList);
            }

            foreach (int k in EnumerableUtility.Range(0, n))
            {
                foreach (Match m in matchesByJ[k])
                {
                    if (m.i > 0)
                    {
                        foreach (int l in optimal.m[m.i - 1].Keys)
                        {
                            update(m, l + 1);
                        }
                    }
                    else
                    {
                        update(m, 1);
                    }
                }
                bruteForceUpdate(k);
            }
            List <Match> optimalMatchSequence = unwind(n);
            int          optimalL             = optimalMatchSequence.Count;

            double guesses = (password.Length == 0) ? 1 : optimal.g[n - 1][optimalL];

            double seconds = guesses / Math.Pow(10, 4);

            double crackTime = PasswordScoring.GuessesToCrackTime(guesses);
            // UnityEngine.Debug.Log($"Seconds: {seconds}");

            // Debug.Log($"Score: {PasswordScoring.GuessesToScore(guesses)}");

            int score = PasswordScoring.GuessesToScore(guesses);

            return(new Result
            {
                Password = password,
                Guesses = guesses,
                GuessesLog10 = Math.Log10(guesses),
                MatchSequence = optimalMatchSequence,
                CrackTime = Math.Round(crackTime, 3),
                CrackTimeDisplay = Utility.DisplayTime(crackTime),
                Score = score
            });
        }