Masterly.Hash.Legacy 2.0.0

dotnet add package Masterly.Hash.Legacy --version 2.0.0
                    
NuGet\Install-Package Masterly.Hash.Legacy -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Masterly.Hash.Legacy" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Masterly.Hash.Legacy" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Masterly.Hash.Legacy" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Masterly.Hash.Legacy --version 2.0.0
                    
#r "nuget: Masterly.Hash.Legacy, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Masterly.Hash.Legacy@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Masterly.Hash.Legacy&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Masterly.Hash.Legacy&version=2.0.0
                    
Install as a Cake Tool

Masterly.Hash.Legacy

Masterly.Hash: password hashing you can't get wrong.

The universal hash migrator for Masterly.Hash. Point it at a password table from another system and your users keep logging in. Each successful login replaces the old hash with an Argon2id Masterly hash. You can also onion-wrap weak hashes offline today, without waiting for anyone to log in.

Format Id Examples Wrappable
BCryptFormat bcrypt $2a$/$2b$/$2y$ from PHP, Spring, Devise, BCrypt.Net yes
WordPressBcryptFormat wordpress-bcrypt $wp$2y$… from WordPress 6.8+ yes
ExternalArgon2Format argon2-phc $argon2id$v=19$m=…,t=…,p=…$… from libsodium, argon2-cffi, PHP no (already strong)
PhpassFormat phpass $P$ WordPress before 6.8, $H$ phpBB3 yes
DjangoPbkdf2Format django-pbkdf2 pbkdf2_sha256$…, pbkdf2_sha1$… yes
AspNetIdentityV3Format aspnet-identity-v3 ASP.NET Core Identity (AQAAAA…) yes
AspNetIdentityV2Format aspnet-identity-v2 ASP.NET Identity 2 / MVC 5 (AA…) yes
UnsaltedDigestFormat.Md5/.Sha1/.Sha256/.Sha512 md5, sha1, sha256, sha512 hex of H(password) yes

It targets net8.0 and depends on BCrypt.Net-Next. Argon2 PHC hashes are verified by the core package's own Argon2 implementation.

Install

dotnet add package Masterly.Hash.Legacy

Upgrade on login

using Masterly.Hash;
using Masterly.Hash.Legacy;

var hasher = new PasswordHasher(new PasswordHasherOptions().AddAllLegacyFormats());
// or only what you have: .AddLegacyFormats(BCryptFormat.Default, PhpassFormat.Default)
// custom caps win over the defaults: .AddLegacyFormats(new BCryptFormat(12)).AddAllLegacyFormats()

PasswordVerificationResult result = hasher.Verify(password, user.PasswordHash);
if (result.IsMatch && result.Rehash is { } fresh)
    user.PasswordHash = fresh;          // now $mh$p=2026a$…

Verification never throws for a bad stored value. An unrecognized or malformed hash fails with FailureReason = UnknownFormat (or Mismatch if the built-in 1.x format claims it; see the security notes).

Onion-wrap weak hashes now

Unsalted MD5 or 1000-iteration PBKDF2 should not wait months for every user to log in. Wrapping computes Argon2id(legacyDigest) offline, without the password. The wrapped hash still verifies with the original password and is replaced with a plain hash on the next login.

foreach (var user in db.Users)
    if (hasher.TryWrap(user.PasswordHash, out string wrapped))
        user.PasswordHash = wrapped;    // $mh$p=2026a,w=md5$…

Keep the format registered for as long as wrapped hashes exist.

Audit a password table

HashAuditReport report = HashAudit.Audit(hasher, db.Users.Select(u => u.PasswordHash));
Console.WriteLine(report.ToMarkdown());

The audit reads the input in a single streaming pass and never throws on garbage rows. It reports counts by format and by profile, how many hashes are recognized, unknown or need a rehash, and how many are weak (unsalted digests, 1.x, phpass, Identity v2, bcrypt below cost 10, PBKDF2 below 100k iterations). It also counts duplicate values, which usually mean unsalted hashes.

Security notes

  • Cost caps. Legacy strings carry their own cost, so an attacker who can write a row could otherwise plant a hash that takes hours to verify. Each format refuses costs above a configurable cap without any hashing: bcrypt and WordPress bcrypt cost ⇐ 15 (new BCryptFormat(maxCost)), phpass log2 ⇐ 18, Django ⇐ 10,000,000 iterations, Identity v3 ⇐ 10,000,000 PBKDF2 block iterations (iterations times output blocks, so a 64-byte HMAC-SHA1 subkey counts four times), and Argon2 m ⇐ 256 MiB (configurable up to 1 GiB), t ⇐ 10, p ⇐ 16. phpass and WordPress also refuse passwords over 4096 bytes, as upstream does, because phpass cost grows with the password length.
  • Argon2 memory. Each concurrent Argon2 verify allocates m KiB, up to MaxConcurrency at once. Size new ExternalArgon2Format(maxMemoryKiB, …) to the host; a failed allocation is reported as a non-match.
  • Refused strings may still be claimed by the 1.x format. The built-in V1LegacyFormat accepts any canonical base64 of 32+ bytes, so an Identity v3 hash this package refuses (over the cap, odd salt or subkey length) is reported as v1: Inspect and HashAudit count it as a recognized, weak v1 hash and Verify returns Mismatch. Do not run a bulk TryWrap with an explicit-algorithm V1LegacyFormat registered while such hashes exist: it would wrap them as w=v1 and those users could never log in again. Raise the cap first.
  • bcrypt truncates passwords at 72 bytes (UTF-8). Until the hash is upgraded, any password with the same first 72 bytes logs in. The upgrade happens on the first successful login. WordPress $wp$ hashes pre-hash with HMAC-SHA384 and are not truncated.
  • Passwords are hashed as raw UTF-8, exactly as the original systems did, with no Unicode normalization. New Masterly hashes use NFKC.
  • All comparisons are constant-time, and secret buffers are zeroed after use.
  • Weak formats only ever verify. Nothing in this package creates a new MD5, phpass or bcrypt hash.
  • Strings are parsed strictly: canonical base64, exact lengths and no leading zeros. $2x$, Argon2 version 16 and Argon2 strings with secret or associated-data parameters are not handled.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Masterly.Hash.Legacy:

Package Downloads
Masterly.Hash.AspNetCore

ASP.NET Core integration for Masterly.Hash: an ASP.NET Core Identity IPasswordHasher<TUser> adapter that keeps verifying existing Identity v2/v3 hashes and upgrades them to Argon2id on login, an IExceptionHandler that maps PasswordHasherBusyException to HTTP 503 with Retry-After, and a minimal API endpoint filter that rejects over-long passwords before hashing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 104 11/3/2026