public void AccountsForTurnPositionsDirectionsAndStartingKey() { var match = new SpatialMatch { Token = "zxcvbn", Graph = "qwerty", Turns = 3, ShiftedCount = 0, i = 1, j = 2, }; var l = match.Token.Length; var s = SpatialGuessesCalculator.KeyboardStartingPositions; var d = SpatialGuessesCalculator.KeyboardAverageDegree; var expected = 0.0; for (var i = 2; i <= l; i++) { for (var j = 1; j <= Math.Min(match.Turns, i - 1); j++) { expected += PasswordScoring.Binomial(i - 1, j - 1) * s * Math.Pow(d, j); } } var actual = SpatialGuessesCalculator.CalculateGuesses(match); actual.Should().Be(expected); }
/// <summary> /// Calculates the number of uppercase variations in the word. /// </summary> /// <param name="token">The token.</param> /// <returns>The number of possible variations.</returns> internal static double UppercaseVariations(string token) { if (token.All(c => char.IsLower(c)) || token.ToLower() == token) { return(1); } if ((char.IsUpper(token.First()) && token.Skip(1).All(c => char.IsLower(c))) || token.All(c => char.IsUpper(c)) || (char.IsUpper(token.Last()) && token.Take(token.Length - 1).All(c => char.IsLower(c)))) { return(2); } var u = token.Count(c => char.IsUpper(c)); var l = token.Count(c => char.IsLower(c)); var variations = 0.0; for (var i = 1; i <= Math.Min(u, l); i++) { variations += PasswordScoring.Binomial(u + l, i); } return(variations); }
/// <summary> /// Calculates the number of l33t variations in the word. /// </summary> /// <param name="match">The match.</param> /// <returns>The number of possible variations.</returns> internal static double L33tVariations(DictionaryMatch match) { if (!match.L33t) { return(1); } var variations = 1.0; foreach (var subbed in match.Sub.Keys) { var unsubbed = match.Sub[subbed]; var s = match.Token.ToLower().Count(c => c == subbed); var u = match.Token.ToLower().Count(c => c == unsubbed); if (s == 0 || u == 0) { variations *= 2; } else { var p = Math.Min(u, s); var possibilities = 0.0; for (var i = 1; i <= p; i++) { possibilities += PasswordScoring.Binomial(u + s, i); } variations *= possibilities; } } return(variations); }
public void BinomialTest() { Assert.AreEqual(1, PasswordScoring.Binomial(0, 0)); Assert.AreEqual(1, PasswordScoring.Binomial(1, 0)); Assert.AreEqual(0, PasswordScoring.Binomial(0, 1)); Assert.AreEqual(1, PasswordScoring.Binomial(1, 1)); Assert.AreEqual(56, PasswordScoring.Binomial(8, 3)); Assert.AreEqual(2598960, PasswordScoring.Binomial(52, 5)); }
/// <summary> /// Estimates the attempts required to guess the password. /// </summary> /// <param name="match">The match.</param> /// <returns>The guesses estimate.</returns> public static double CalculateGuesses(SpatialMatch match) { int s; double d; if (match.Graph == "qwerty" || match.Graph == "dvorak") { s = KeyboardStartingPositions; d = KeyboardAverageDegree; } else { s = KeypadStartingPositions; d = KeypadAverageDegree; } double guesses = 0; var l = match.Token.Length; var t = match.Turns; for (var i = 2; i <= l; i++) { var possibleTurns = Math.Min(t, i - 1); for (var j = 1; j <= possibleTurns; j++) { guesses += PasswordScoring.Binomial(i - 1, j - 1) * s * Math.Pow(d, j); } } if (match.ShiftedCount > 0) { var shifted = match.ShiftedCount; var unshifted = match.Token.Length - match.ShiftedCount; if (shifted == 0 || unshifted == 0) { guesses *= 2; } else { double variations = 0; for (var i = 1; i <= Math.Min(shifted, unshifted); i++) { variations += PasswordScoring.Binomial(shifted + unshifted, i); } guesses *= variations; } } return(guesses); }
/// <summary> /// Calculate entropy for a math that was found on this adjacency graph /// </summary> public double CalculateEntropy(int matchLength, int turns, int shiftedCount) { // This is an estimation of the number of patterns with length of matchLength or less with turns turns or less var possibilities = Enumerable.Range(2, matchLength - 1).Sum(i => { var possible_turns = Math.Min(turns, i - 1); return(Enumerable.Range(1, possible_turns).Sum(j => { return StartingPositions * Math.Pow(AverageDegree, j) * PasswordScoring.Binomial(i - 1, j - 1); })); }); var entropy = Math.Log(possibilities, 2); // Entropy increaeses for a mix of shifted and unshifted if (shiftedCount > 0) { var unshifted = matchLength - shiftedCount; entropy += Math.Log(Enumerable.Range(0, Math.Min(shiftedCount, unshifted) + 1).Sum(i => PasswordScoring.Binomial(matchLength, i)), 2); } return(entropy); }
public void PasswordScoringBinomialScoresCorrectly(int n, int k, int score) { PasswordScoring.Binomial(n, k).Should().Be(score); }
// ReSharper disable once InconsistentNaming private static void CalulateL33tEntropy(L33tDictionaryMatch match) { // I'm a bit dubious about this function, but I have duplicated zxcvbn functionality regardless var possibilities = 0; foreach (var kvp in match.Subs) { var subbedChars = match.Token.Count(c => c == kvp.Key); var unsubbedChars = match.Token.Count(c => c == kvp.Value); // Won't this always be zero? possibilities += Enumerable.Range(0, Math.Min(subbedChars, unsubbedChars) + 1).Sum(i => (int)PasswordScoring.Binomial(subbedChars + unsubbedChars, i)); } var entropy = Math.Log(possibilities, 2); // In the case of only a single subsitution (e.g. 4pple) this would otherwise come out as zero, so give it one bit match.L33tEntropy = (entropy < 1 ? 1 : entropy); match.Entropy += match.L33tEntropy; // We have to recalculate the uppercase entropy -- the password matcher will have used the subbed password not the original text match.Entropy -= match.UppercaseEntropy; match.UppercaseEntropy = PasswordScoring.CalculateUppercaseEntropy(match.Token); match.Entropy += match.UppercaseEntropy; }