Skip to content

Commit

Permalink
Fixed some discrepancies with the typescript library.
Browse files Browse the repository at this point in the history
Also made compatible with .Net Standard 1.3
  • Loading branch information
Tony Richards committed Oct 28, 2020
1 parent fa8dec9 commit cfd1b29
Show file tree
Hide file tree
Showing 15 changed files with 66 additions and 66 deletions.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Copyright (c) 2012 Dropbox, Inc.
Copyright (c) 2020 Tony Richards

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
53 changes: 10 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,69 +25,36 @@ From the `Zxcvbn` readme:
>
> http://tech.dropbox.com/?p=165
This port aims to produce comparable results with the JS version of `Zxcvbn`. The results
structure that is returned can be interpreted in the same way as with JS `Zxcvbn` and this
port has been tested with a variety of passwords to ensure that it return the same results
as the JS version.

There are some implementation differences, however, so exact results are not guaranteed.
This port aims to produce comparable results with the Typescript version of `Zxcvbn` which I have also put out and is here https://github.com/trichards57/zxcvbn.
The results structure that is returned can be interpreted in the same way as with JS `Zxcvbn` and this port has been tested with a variety of passwords to ensure
that it return the same score as the JS version (some other details vary a little).

I have tried to keep the implementation as close as possible, but there is still a chance of some small changes. Let me know if you find any differences
and I can investigate.

### Using `Zxcvbn-cs`

The included Visual Studio project will create a single assembly, Zxcvbn.dll, which is all that is
required to be included in your project.

To evaluate a single password:

``` C#
using Zxcvbn;

//...
var result = Zxcvbn.MatchPassword("p@ssw0rd");
```

To evaluate many passwords, create an instance of `Zxcvbn` and then use that to evaluate your passwords.
This avoids reloading dictionaries etc. for every password:
To evaluate a password:

``` C#
using Zxcvbn;

//...
var zx = new Zxcvbn();

foreach (var password in passwords)
{
var result = zx.EvaluatePassword(password);

//...
}
var result = Zxcvbn.Core.EvaluatePassword("p@ssw0rd");
```

Both `MatchPassword` and `EvaluatePassword` take an optional second parameter that contains an enumerable of
`EvaluatePassword` takes an optional second parameter that contains an enumerable of
user data strings to also match the password against.

### Interpreting Results

The `Result` structure returned from password evaluation is interpreted the same way as with JS `Zxcvbn`:

- `result.Entropy`: bits of entropy for the password
- `result.CrackTime`: an estimation of actual crack time, in seconds.
- `result.CrackTimeDisplay`: the crack time, as a friendlier string: "instant", "6 minutes", "centuries", etc.
- `result.Score`: [0,1,2,3,4] if crack time is less than [10\*\*2, 10\*\*4, 10\*\*6, 10\*\*8, Infinity]. (useful for implementing a strength bar.)
- `result.MatchSequence`: the list of pattern matches that was used to calculate Entropy.
- `result.CalculationTime`: how long `Zxcvbn` took to calculate the results.

### More Information

For more information on why password entropy is calculated as it is, refer to `Zxcvbn`s originators:

https://github.com/lowe/zxcvbn

http://tech.dropbox.com/?p=165
The `Result` structure returned from password evaluation is interpreted the same way as with JS `Zxcvbn`.

- `result.Score`: 0-4 indicating the estimated strength of the password.

### Licence

Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 2.0.{build}
version: 5.0.{build}
image: Visual Studio 2019
init:
- git config --global core.autocrlf true
Expand Down
14 changes: 14 additions & 0 deletions zxcvbn-core-test-console/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Zxcvbn.TestConsole
{
internal class Program
{
private static void Main(string[] args)
{
var result = Zxcvbn.Core.EvaluatePassword("Applesoranges!");

Console.WriteLine(result.Score);
}
}
}
14 changes: 14 additions & 0 deletions zxcvbn-core-test-console/zxcvbn-core-test-console.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Zxcvbn.TestConsole</RootNamespace>
<AssemblyName>zxcvbn-core-test-console</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\zxcvbn-core\zxcvbn-core.csproj" />
</ItemGroup>
</Project>
3 changes: 1 addition & 2 deletions zxcvbn-core/Feedback.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Zxcvbn.Matcher.Matches;

Expand Down Expand Up @@ -93,7 +92,7 @@ private static FeedbackItem GetDictionaryMatchFeedback(DictionaryMatch match, bo
var word = match.Token;
if (char.IsUpper(word[0]))
suggestions.Add("Capitalization doesn't help very much");
else if (word.All(c => char.IsUpper(c)) && word.ToLower(CultureInfo.InvariantCulture) != word)
else if (word.All(c => char.IsUpper(c)) && word.ToLower() != word)
suggestions.Add("All-uppercase is almost as easy to guess as all-lowercase");

if (match.Reversed && match.Token.Length >= 4)
Expand Down
2 changes: 2 additions & 0 deletions zxcvbn-core/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "To match this project's coding style.")]
[assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "This normalization isn't security critical")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "No header required.")]
[assembly: SuppressMessage("Globalization", "CA1304:Specify CultureInfo", Justification = "Not supported in all the desired target libraries.")]
[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not supported in all the desired target libraries.")]
5 changes: 2 additions & 3 deletions zxcvbn-core/Matcher/DictionaryMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Zxcvbn.Matcher.Matches;
Expand Down Expand Up @@ -42,7 +41,7 @@ public DictionaryMatcher(string name, IEnumerable<string> wordList)
dictionaryName = name;

// Must ensure that the dictionary is using lowercase words only
rankedDictionary = BuildRankedDictionary(wordList.Select(w => w.ToLower(CultureInfo.InvariantCulture)));
rankedDictionary = BuildRankedDictionary(wordList.Select(w => w.ToLower()));
}

/// <summary>
Expand All @@ -52,7 +51,7 @@ public DictionaryMatcher(string name, IEnumerable<string> wordList)
/// <returns>An enumerable of dictionary matches.</returns>
public virtual IEnumerable<Match> MatchPassword(string password)
{
var passwordLower = password.ToLower(CultureInfo.InvariantCulture);
var passwordLower = password.ToLower();
var length = passwordLower.Length;
var matches = new List<Match>();

Expand Down
2 changes: 1 addition & 1 deletion zxcvbn-core/Matcher/L33tMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public IEnumerable<Match> MatchPassword(string password)
foreach (DictionaryMatch match in matcher.MatchPassword(subbedPassword))
{
var token = password.Substring(match.i, match.j - match.i + 1);
if (token.Equals(match.MatchedWord, StringComparison.InvariantCultureIgnoreCase))
if (token.ToLower().Equals(match.MatchedWord.ToLower()))
continue;

var matchSub = new Dictionary<char, char>();
Expand Down
10 changes: 8 additions & 2 deletions zxcvbn-core/Result.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Zxcvbn.Matcher.Matches;

namespace Zxcvbn
Expand Down Expand Up @@ -31,7 +32,12 @@ public class Result
/// <summary>
/// Gets the number of guesses the password is estimated to need.
/// </summary>
public double Guesses { get; internal set; }
public long Guesses { get; internal set; }

/// <summary>
/// Gets log10(the number of guesses) the password is estimated to need.
/// </summary>
public double GuessesLog10 => Math.Log10(Guesses);

/// <summary>
/// Gets the sequence of matches that were used to assess the password.
Expand Down
6 changes: 3 additions & 3 deletions zxcvbn-core/Scoring/BruteForceGuessesCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ internal class BruteForceGuessesCalculator
/// <returns>The guesses estimate.</returns>
public static long CalculateGuesses(BruteForceMatch match)
{
var guesses = (long)Math.Pow(BruteforceCardinality, match.Token.Length);
var guesses = Math.Pow(BruteforceCardinality, match.Token.Length);
if (double.IsPositiveInfinity(guesses))
guesses = long.MaxValue;
guesses = double.MaxValue;

var minGuesses = match.Token.Length == 1 ? MinSubmatchGuessesSingleCharacter + 1 : MinSubmatchGuessesMultiCharacter + 1;

return Math.Max(guesses, minGuesses);
return (long)Math.Max(guesses, minGuesses);
}
}
}
7 changes: 3 additions & 4 deletions zxcvbn-core/Scoring/DictionaryGuessesCalculator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Globalization;
using System.Linq;
using Zxcvbn.Matcher.Matches;

Expand Down Expand Up @@ -40,8 +39,8 @@ internal static long L33tVariations(DictionaryMatch match)
foreach (var subbed in match.Sub.Keys)
{
var unsubbed = match.Sub[subbed];
var s = match.Token.ToLower(CultureInfo.InvariantCulture).Count(c => c == subbed);
var u = match.Token.ToLower(CultureInfo.InvariantCulture).Count(c => c == unsubbed);
var s = match.Token.ToLower().Count(c => c == subbed);
var u = match.Token.ToLower().Count(c => c == unsubbed);

if (s == 0 || u == 0)
{
Expand All @@ -67,7 +66,7 @@ internal static long L33tVariations(DictionaryMatch match)
/// <returns>The number of possible variations.</returns>
internal static long UppercaseVariations(string token)
{
if (token.All(c => char.IsLower(c)) || token.ToLower(CultureInfo.InvariantCulture) == 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)))
Expand Down
6 changes: 3 additions & 3 deletions zxcvbn-core/TimeEstimates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public static AttackTimes EstimateAttackTimes(double guesses)
{
var crackTimesSeconds = new CrackTimes
{
OfflineFastHashing1e10PerSecond = guesses / (100 / 3600),
OfflineFastHashing1e10PerSecond = guesses / (100.0 / 3600),
OfflineSlowHashing1e4PerSecond = guesses / 10,
OnlineNoThrottling10PerSecond = guesses / 1e4,
OnlineThrottling100PerHour = guesses / 1e10,
};
var crackTimesDisplay = new CrackTimesDisplay
{
OfflineFastHashing1e10PerSecond = DisplayTime(guesses / (100 / 3600)),
OfflineFastHashing1e10PerSecond = DisplayTime(guesses / (100.0 / 3600)),
OfflineSlowHashing1e4PerSecond = DisplayTime(guesses / 10),
OnlineNoThrottling10PerSecond = DisplayTime(guesses / 1e4),
OnlineThrottling100PerHour = DisplayTime(guesses / 1e10),
Expand All @@ -44,7 +44,7 @@ private static string DisplayTime(double seconds)
const double day = hour * 24;
const double month = day * 31;
const double year = month * 12;
const double century = year * 1000;
const double century = year * 100;

int? displayNumber = null;
string displayString;
Expand Down
5 changes: 2 additions & 3 deletions zxcvbn-core/zxcvbn-core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>C#/.NET port of Dan Wheeler/DropBox's Zxcvbn JS password strength estimation library. Updated for .Net Core.</Description>
<Authors>mickford;Tony Richards (trichards57);Dan Wheeler;DropBox</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;net451;netstandard1.3</TargetFrameworks>
<AssemblyName>zxcvbn-core</AssemblyName>
<PackageId>zxcvbn-core</PackageId>
<PackageTags>password;strength;validation;zxcvbn</PackageTags>
Expand Down Expand Up @@ -37,5 +37,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
</Project>
2 changes: 1 addition & 1 deletion zxcvbn-cs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core", "zxcvbn-core\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core-test", "zxcvbn-core-test\zxcvbn-core-test.csproj", "{65B256F9-4874-4D6F-9A46-D881FAB0215B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zxcvbn-core-list-builder", "zxcvbn-core-list-builder\zxcvbn-core-list-builder.csproj", "{80BA1964-B98A-4D34-95B8-DFA51CD3A378}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core-list-builder", "zxcvbn-core-list-builder\zxcvbn-core-list-builder.csproj", "{80BA1964-B98A-4D34-95B8-DFA51CD3A378}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down

0 comments on commit cfd1b29

Please sign in to comment.