Zxcvbn 0.1.1
dotnet add package Zxcvbn --version 0.1.1
NuGet\Install-Package Zxcvbn -Version 0.1.1
<PackageReference Include="Zxcvbn" Version="0.1.1" />
<PackageVersion Include="Zxcvbn" Version="0.1.1" />
<PackageReference Include="Zxcvbn" />
paket add Zxcvbn --version 0.1.1
#r "nuget: Zxcvbn, 0.1.1"
#:package Zxcvbn@0.1.1
#addin nuget:?package=Zxcvbn&version=0.1.1
#tool nuget:?package=Zxcvbn&version=0.1.1
Zxcvbn
Realistic password strength estimation for .NET. A faithful, modern port of zxcvbn-ts (the maintained fork of Dropbox's zxcvbn).
Composition rules (require an uppercase letter, a digit and a symbol) are security theater.
They reject correcthorsebatterystaple and wave through P@ssw0rd1, which is exactly
backwards. Zxcvbn scores what attackers actually try: it matches a password against ranked
frequency dictionaries, keyboard walks, repeats, sequences, dates and years, including
reversed and l33t-substituted spellings, then runs the exact minimum-guess search to produce
a single 0 to 4 score with honest crack-time estimates.
using Zxcvbn;
Result result = Core.EvaluatePassword("Tr0ub4dour&3");
Console.WriteLine(result.Score); // 4
Console.WriteLine(result.GuessesLog10); // ~11.6
Console.WriteLine(result.CrackTimesDisplay.OfflineSlowHashing1e4PerSecond); // "1 year"
Console.WriteLine(result.CrackTimesDisplay.OnlineThrottling100PerHour); // "centuries"
Console.WriteLine(result.Feedback.Warning); // null for a strong password
Why this package exists
zxcvbn is the de facto standard for password strength estimation. The JavaScript packages
alone see millions of downloads a week, and there are ports in Go, Java, Rust and Python. On
.NET the picture is worse: zxcvbn-core has not shipped since February 2021 (after 3.8M
downloads, so the demand is real), and zxcvbn.net is stuck at 0.0.16. Neither tracks the
improvements in the maintained zxcvbn-ts line. This package is a clean port of zxcvbn-ts 4.x.
NIST SP 800-63B recommends checking new passwords against lists of common and compromised values rather than imposing composition rules. Zxcvbn is the strength half of that recommendation.
Install
dotnet add package Zxcvbn
The package is a few megabytes because the ranked dictionaries are the product: common
passwords, English words, first names, surnames, wikipedia terms, a diceware list and word
sequences, plus six keyboard adjacency graphs, all embedded and compressed. A future
Zxcvbn.Minimal without the bundled dictionaries is on the roadmap.
Score meaning
| Score | Meaning | Guesses | Guidance |
|---|---|---|---|
| 0 | Too guessable | < 1e3 | Risky. Reject. |
| 1 | Very guessable | < 1e6 | Protects only against throttled online attacks. |
| 2 | Somewhat guessable | < 1e8 | Protects against unthrottled online attacks. |
| 3 | Safely unguessable | < 1e10 | Moderate protection against offline slow-hash attacks. |
| 4 | Very unguessable | >= 1e10 | Strong protection against offline attacks. |
The thresholds carry a small delta, exactly as zxcvbn does: the boundaries are 1e3 + 5,
1e6 + 5, 1e8 + 5 and 1e10 + 5 guesses, so a password sitting precisely on a power-of-ten
lands in the lower score.
Each result also carries crack-time estimates for four scenarios: online with throttling (100 guesses/hour), online without throttling (10/second), offline with a slow hash such as bcrypt or argon2 (1e4/second) and offline with a fast hash (1e10/second), both as raw seconds and as human-readable strings.
Reuse the analyzer
Core.EvaluatePassword is a convenience wrapper over a shared default analyzer. To customise
behavior, build a ZxcvbnAnalyzer once and reuse it. The analyzer is immutable and
thread-safe, so a single instance can serve your whole application.
using Zxcvbn;
var options = new ZxcvbnOptions();
options.AddDictionary("company", new[] { "acmecorp", "acme", "roadrunner" });
var analyzer = new ZxcvbnAnalyzer(options);
Result result = analyzer.Evaluate("acmecorp2024");
User inputs
Passwords built from data an attacker can guess about a specific user (their name, email,
username or your application and company names) should be penalised heavily. Pass those values
as userInputs and they are treated as a per-evaluation dictionary.
using Zxcvbn;
var analyzer = new ZxcvbnAnalyzer();
// "Acme2024" scores 2 on its own, but only 1 once "Acme" is a known input.
Result withoutContext = analyzer.Evaluate("Acme2024");
Result withContext = analyzer.Evaluate("Acme2024", new[] { "Acme" });
ASP.NET Core Identity integration
The most common request is to gate registration on strength. Implement
IPasswordValidator<TUser> and register it. This is the whole integration:
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Zxcvbn;
public sealed class ZxcvbnPasswordValidator<TUser> : IPasswordValidator<TUser>
where TUser : class
{
private readonly ZxcvbnAnalyzer _analyzer = new();
private readonly int _minimumScore;
public ZxcvbnPasswordValidator(int minimumScore = 3) => _minimumScore = minimumScore;
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user, string? password)
{
if (password is null)
{
return Task.FromResult(IdentityResult.Failed(new IdentityError
{
Code = "PasswordRequired",
Description = "A password is required.",
}));
}
// Feed known user data in so passwords built from it are penalised.
var userInputs = new List<string>();
string? userName = manager.GetUserNameAsync(user).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(userName))
{
userInputs.Add(userName);
}
Result result = _analyzer.Evaluate(password, userInputs);
if (result.Score >= _minimumScore)
{
return Task.FromResult(IdentityResult.Success);
}
string message = result.Feedback.Warning ?? "This password is too easy to guess.";
if (result.Feedback.Suggestions.Count > 0)
{
message += " " + string.Join(" ", result.Feedback.Suggestions);
}
return Task.FromResult(IdentityResult.Failed(new IdentityError
{
Code = "PasswordTooWeak",
Description = message,
}));
}
}
Register it alongside the built-in validators:
builder.Services.AddIdentityCore<ApplicationUser>()
.AddPasswordValidator<ZxcvbnPasswordValidator<ApplicationUser>>();
Inspecting the details
Every match that makes up the optimal cover is exposed on Result.Sequence, typed per
pattern, so you can build a strength meter that explains itself.
using Zxcvbn;
Result result = Core.EvaluatePassword("qwerty12/12/2012");
foreach (Match match in result.Sequence)
{
string extra = match switch
{
DictionaryMatch d => $"word '{d.MatchedWord}' from {d.DictionaryName}",
SpatialMatch s => $"keyboard walk on {s.Graph}",
DateMatch date => $"date {date.Year}-{date.Month}-{date.Day}",
_ => match.Pattern,
};
Console.WriteLine($"{match.Token}: {extra}");
}
Notes and honesty
- The bundled dictionaries are English and common-password oriented. Other languages will score less accurately until language packs are added (roadmap).
- Package size comes from the embedded dictionaries. They are the product.
- Guess estimates are computed with the same formulas as zxcvbn-ts. For ASCII and common
Unicode this release has exact parity with zxcvbn-ts 4.1.2 as of reference year 2026: the
score and the selected match sequence match exactly, and
GuessesLog10agrees to within IEEE double rounding (about 2e-15). This is validated by a development-time differential of over twelve thousand passwords against the reference, of which 1,508 are committed as fixtures. TinyGuessesLog10differences are possible where floating-point evaluation order differs. - The one exception to that parity is characters whose Unicode case-fold changes string length:
İ(U+0130), theff fi fl ffi ffl ſt stligatures,ʼn(U+0149) andẞ(U+1E9E). .NET's invariant case-fold keeps these length 1, while JavaScript expands them, which corrupts zxcvbn-ts's own internal dictionary index mapping. On the handful of contrived inputs that combine such a character with dictionary words the two implementations diverge, and the port may rate marginally stronger because it does not reproduce that reference behavior. Realistic single-character cases score at parity or more conservatively. - The reference year used by the date and recent-year heuristics is pinned per release (currently 2026) for reproducible results, whereas live zxcvbn-ts reads the current calendar year. They stay identical within the release year and drift by at most a year or two for year-bearing passwords after that. Each annual release bumps the pinned year and regenerates the fixtures; a build guard fails the tests if the pin ever falls more than a year behind.
- Evaluation is fast for typical passwords (a few milliseconds), but a maximally adversarial
input at the length cap can take up to about a second (still far faster than zxcvbn-ts).
For attacker-controlled input, run
Evaluateoff the request thread; the 256-character cap bounds the worst case. A configurable work ceiling is on the roadmap. - This is a strength estimator, not a breach check. It does not tell you whether a specific
password has appeared in a data breach. Pair it with a Have I Been Pwned k-anonymity lookup
for that. A companion
Zxcvbn.Pwnedpackage is on the roadmap.
Roadmap
- More language packs.
Zxcvbn.Minimalwithout the bundled dictionaries, for callers who supply their own.Zxcvbn.Pwned, a Have I Been Pwned k-anonymity companion.
Credits
The algorithm and the dictionaries are the work of the zxcvbn and zxcvbn-ts authors. The
algorithm was ported from @zxcvbn-ts/core; the dictionaries are sourced from
@zxcvbn-ts/language-common and @zxcvbn-ts/language-en, and the original algorithm is
Dropbox's zxcvbn. All are MIT licensed. Their full copyright notices are reproduced in
THIRD-PARTY-NOTICES.md, which ships inside the NuGet package.
License
MIT. See LICENSE. This package also bundles MIT-licensed third-party data; see THIRD-PARTY-NOTICES.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.