Argon2id.PasswordHasher 1.0.0

dotnet add package Argon2id.PasswordHasher --version 1.0.0
                    
NuGet\Install-Package Argon2id.PasswordHasher -Version 1.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="Argon2id.PasswordHasher" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Argon2id.PasswordHasher" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Argon2id.PasswordHasher" />
                    
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 Argon2id.PasswordHasher --version 1.0.0
                    
#r "nuget: Argon2id.PasswordHasher, 1.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 Argon2id.PasswordHasher@1.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=Argon2id.PasswordHasher&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Argon2id.PasswordHasher&version=1.0.0
                    
Install as a Cake Tool

<div align="center">

๐Ÿ” Argon2id.PasswordHasher

The opinionated, secure-by-default Argon2id password hasher for .NET 8, 9, and 10.

CI CodeQL Pages OpenSSF Scorecard Fuzzing Differential Mutation NuGet NuGet (AspNetCore) License: MIT .NET AOT

Memory-hard password hashing that's hard to get wrong.

</div>


Argon2id.PasswordHasher wraps a vetted Argon2id implementation in a tiny, hard-to-misuse API with strong defaults out of the box. You get memory-hard resistance to GPU/ASIC cracking (RFC 9106 / OWASP-aligned), self-describing hashes that carry their own parameters, constant-time verification, an optional keyed pepper with rotation, and a one-line ASP.NET Core Identity integration โ€” without having to become a cryptographer first.

using Argon2id.PasswordHasher;

var hasher = new Argon2idPasswordHasher();

string stored = hasher.HashPassword("correct horse battery staple");
//   $argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>

bool ok = hasher.VerifyPassword("correct horse battery staple", stored); // true

Try the demo

There are three runnable samples, each aimed at a different question:

Sample Where it runs When to use it
Live WASM demo In your browser, no install Quickest way to try the library. Hashing runs on your CPU via WebAssembly. Auto-deployed to GitHub Pages on every push.
API documentation DocFx, browser Every public type + member, generated from XML doc comments. Co-published with the live demo.
samples/Argon2id.PasswordHasher.IdentityMigrationSample Console (local) The adoption path. Watch a real UserManager verify a stock PBKDF2 hash and transparently rewrite it as peppered Argon2id on first login โ€” zero resets, zero downtime. Copy its wiring into your app.
samples/Argon2id.PasswordHasher.Demo Blazor Server (local) Shows production-shape integration: DI, antiforgery, rate limiting, HSTS, CSP, constant-time login, memory-cost DoS gate.
samples/Argon2id.PasswordHasher.WasmDemo Blazor WebAssembly (local) Same UX as the live demo, but running against the in-tree library. Edit and refresh.

Run any locally:

git clone https://github.com/systemslibrarian/argon2id-passwordhasher.git
cd argon2id-passwordhasher

# The migration walkthrough (PBKDF2 โ†’ peppered Argon2id via real Identity):
dotnet run --project samples/Argon2id.PasswordHasher.IdentityMigrationSample

# Server flavor (production-shape, hardened):
dotnet run --project samples/Argon2id.PasswordHasher.Demo

# WASM flavor (same UX as the live demo):
dotnet run --project samples/Argon2id.PasswordHasher.WasmDemo

All samples use a ProjectReference to the in-tree library, so they always exercise the version you're working on. They are demos, not starter templates โ€” users live in memory and hash internals are shown on screen for educational clarity (see each sample's own README).

Documentation

Documentation is layered so you only read the depth you need:

Audience / question Doc
"How do I use it?" This README, plus API reference
"How do I migrate from Identity's default PasswordHasher?" MIGRATION.md
"How should I run this in production?" OPERATIONS.md โ€” capacity planning, monitoring, alerting, failure modes
"What about FIPS / SOC 2 / vendor questionnaires?" COMPLIANCE.md
"What is the security threat model?" THREAT-MODEL.md
"How do I store the pepper in Azure Key Vault / AWS / Vault?" docs/pepper-key-management.md
"What's the support and lifecycle policy?" SUPPORT.md
"How do I tune the work factor?" docs/parameter-tuning.md
"What does the library deliberately NOT do?" KNOWN-GAPS.md
"How do I report a vulnerability?" SECURITY.md
"How do I contribute?" CONTRIBUTING.md
"What changed in this version?" CHANGELOG.md

Table of contents

Why this library

๐Ÿ›ก๏ธ Secure by default The no-arg constructor gives you 64 MiB / t=3 / p=1 โ€” comfortably above the OWASP minimum. No footguns to configure.
๐Ÿ“ฆ Self-describing hashes Every hash is a standard PHC string containing its own parameters, so verification never breaks when you raise the work factor.
โ™ป๏ธ Built-in upgrade path NeedsRehash tells you when a stored hash is weaker than your current settings (or uses an old pepper) so you can upgrade users transparently.
โฑ๏ธ Constant-time Final comparison uses CryptographicOperations.FixedTimeEquals.
๐ŸŒถ๏ธ Pepper with rotation Optional keyed secret kept outside your database, with first-class key rotation.
๐Ÿงฉ ASP.NET Core ready Drop-in IPasswordHasher<TUser> + a one-line DI extension.
๐Ÿš€ Trim + AOT compatible Marked IsTrimmable and IsAotCompatible so Native AOT consumers just work.
๐Ÿงผ Tiny & honest One runtime dependency. Every real limitation is documented in KNOWN-GAPS.md.

Packages

Package Purpose Depends on
Argon2id.PasswordHasher Core hasher โ€” no web/framework dependency Konscious.Security.Cryptography.Argon2
Argon2id.PasswordHasher.AspNetCore IPasswordHasher<TUser> adapter + DI extension the core package + Microsoft.Extensions.Identity.Core

Both packages target net8.0, net9.0, and net10.0.

Install

# Core
dotnet add package Argon2id.PasswordHasher --prerelease

# Optional: ASP.NET Core Identity integration
dotnet add package Argon2id.PasswordHasher.AspNetCore --prerelease

Quick start

Registration โ€” hash the password and store the returned string:

var hasher = new Argon2idPasswordHasher();
user.PasswordHash = hasher.HashPassword(password);
await db.SaveChangesAsync();

Login โ€” verify against the stored string:

if (!hasher.VerifyPassword(password, user.PasswordHash))
    return Unauthorized();

// Optionally strengthen the stored hash if your settings have grown:
if (hasher.NeedsRehash(user.PasswordHash))
{
    user.PasswordHash = hasher.HashPassword(password);
    await db.SaveChangesAsync();
}

VerifyPassword never throws on bad input: a null, empty, malformed, or non-Argon2id stored value simply returns false.

Argon2idPasswordHasher is stateless and thread-safe. Create one and reuse it (e.g. register it as a singleton) rather than constructing one per request.

The hash format

Hashes are emitted in the standard PHC string format used by libsodium and the Argon2 reference implementation:

$argon2id$v=19$m=65536,t=3,p=1$<base64 salt>$<base64 hash>
โ””โ”€โ”€ alg โ”€โ”€โ”˜โ”” ver โ”˜โ””โ”€โ”€ cost params โ”€โ”€โ”˜โ””โ”€โ”€ salt โ”€โ”€โ”˜โ””โ”€โ”€ hash โ”€โ”€โ”˜

Because the parameters live inside the string, the hash is portable and self-verifying: you can change your defaults whenever you like and old hashes keep validating with the parameters they were created with. When a pepper is used, an extra keyid=<id> parameter is added so verification can select the right secret.

Configuration

Pass an Argon2idOptions to tune the work factor. Invalid values (below the safe minimums) throw ArgumentOutOfRangeException at construction โ€” fail fast, not silently weak.

var hasher = new Argon2idPasswordHasher(new Argon2idOptions
{
    MemorySizeKib       = 131072, // 128 MiB
    Iterations          = 4,
    DegreeOfParallelism = 1,
});
Option Default Minimum Notes
MemorySizeKib 65536 (64 MiB) 8192 Primary GPU/ASIC defense. Raise this first.
Iterations 3 1 Passes over memory (linear CPU cost).
DegreeOfParallelism 1 1 Lanes/threads per hash. Keep low on shared servers.
SaltSizeBytes 16 (128-bit) 16 RFC 9106 recommendation.
HashSizeBytes 32 (256-bit) 16 Output (tag) length.

Why these defaults? They're a strong, general-purpose baseline for 2026 server hardware that exceeds the current OWASP minimum (Argon2id, 19 MiB, t=2, p=1) while keeping p=1 so per-hash CPU stays predictable under concurrent logins. They are not tuned to your machine โ€” measure and adjust. See docs/parameter-tuning.md and the benchmark project.

Argon2idOptions.Recommended exposes the defaults explicitly.

Upgrading work factor (rehash on login)

Security guidance gets stronger over time. When you raise your parameters, existing users upgrade themselves the next time they sign in โ€” no mass migration, no broken logins:

if (hasher.VerifyPassword(password, user.PasswordHash))
{
    if (hasher.NeedsRehash(user.PasswordHash))
        user.PasswordHash = hasher.HashPassword(password); // re-hashed with current settings
    // sign the user in...
}

NeedsRehash returns true when the stored hash is unparseable, any stored parameter is below your current configuration, or (with a pepper ring) the hash doesn't use your active pepper.

Avoiding string passwords (span overloads)

HashPassword / VerifyPassword also accept ReadOnlySpan<char> and ReadOnlySpan<byte>, so you can hash a credential without ever materializing it as a string. The hasher zeroes every password-derived buffer it owns.

ReadOnlySpan<char> pw = GetPasswordChars();
string hash = hasher.HashPassword(pw);
bool ok    = hasher.VerifyPassword(pw, hash);

.NET cannot reliably wipe an immutable string from memory. Prefer the span overloads when the password doesn't otherwise need to be a string. See KNOWN-GAPS.md ยง1.

Pepper (secret key) with rotation

A pepper is an application secret mixed into every hash and kept outside the database (in a key vault, KMS, or environment variable). If your password table leaks but the pepper doesn't, the stolen hashes can't be cracked offline. Peppers here are keyed and rotatable โ€” each hash records which pepper produced it (via the PHC keyid), and the key bytes are never stored.

Lose your pepper ring, lose your users. The library never persists pepper keys โ€” that's your responsibility. Hashes made with a pepper key cannot be verified without that key's bytes; there is no recovery path. If the active key is lost, every user whose hash was produced under it must go through a password reset. Back up active and retired pepper material to a separate trust domain before you enable peppering. See docs/pepper-key-management.md and KNOWN-GAPS.md ยง2.

byte[] key = GetPepperFromVault();             // โ‰ฅ 16 bytes, kept secret
var ring   = new PepperRing(new Pepper("2026-05", key));
var hasher = new Argon2idPasswordHasher(Argon2idOptions.Recommended, ring);

string hash = hasher.HashPassword(password);
//   $argon2id$v=19$m=65536,t=3,p=1,keyid=<id>$<salt>$<hash>

Rotation โ€” promote a new active key and keep the old one as retired so existing hashes still verify; NeedsRehash then upgrades them on the next login:

var rotated = new PepperRing(
    active:  new Pepper("2026-11", newKey),
    retired: new Pepper("2026-05", oldKey));

For end-to-end examples of loading peppers from Azure Key Vault, AWS Secrets Manager, Google Cloud Secret Manager, HashiCorp Vault, or environment variables, plus the full rotation playbook, see docs/pepper-key-management.md.

ASP.NET Core Identity

Install Argon2id.PasswordHasher.AspNetCore and register the hasher in one line:

builder.Services
    .AddIdentityCore<IdentityUser>()
    .Services
    .AddArgon2idPasswordHasher<IdentityUser>(); // optional: pass Argon2idOptions and/or a PepperRing

This registers an IPasswordHasher<TUser> backed by Argon2id and shares a single core hasher as a singleton. Verification maps cleanly onto Identity's contract:

Result When
Success Password matches and the hash is up to date.
SuccessRehashNeeded Password matches but the hash is weaker than current settings / uses an old pepper. Identity rehashes it automatically.
Failed Password doesn't match, or the stored value is malformed.

Trimming & Native AOT

Both packages are marked IsTrimmable=true and IsAotCompatible=true. No reflection, no dynamic codegen, no System.Reflection.Emit โ€” the library uses only BCL crypto primitives plus the Konscious managed Argon2 implementation. Native AOT consumers can publish trimmed binaries without warnings.

How this library compares

Quick orientation for the common alternatives in the .NET ecosystem:

Library Algorithm Memory-hard Self-describing hash First-class pepper ASP.NET Identity adapter TFMs
Argon2id.PasswordHasher (this library) Argon2id (RFC 9106) โœ… โœ… (PHC) โœ… + rotation โœ… + migration adapter net8 / net9 / net10
Microsoft.AspNetCore.Identity.PasswordHasher<TUser> (the default) PBKDF2 HMAC-SHA-512, 100k iterations โŒ semi (version byte prefix) โŒ n/a (it is the default) every .NET they support
Konscious.Security.Cryptography.Argon2 (raw) Argon2i / Argon2d / Argon2id โœ… โŒ (raw bytes) manual โŒ netstandard2.0
BCrypt.Net-Next bcrypt โŒ โœ… ($2a$โ€ฆ) โŒ community wrappers netstandard2.0
Isopoh.Cryptography.Argon2 Argon2i / Argon2d / Argon2id โœ… โœ… (encoded) โŒ โŒ netstandard2.0

When this library is the right choice: you want Argon2id specifically (per OWASP's current top recommendation for password storage), with self-describing PHC strings so you can raise the work factor without breaking existing users, optional first-class pepper with rotation, and a drop-in IPasswordHasher<TUser> so an existing ASP.NET Core Identity app can migrate with one line.

When the default PasswordHasher<TUser> is the right choice: you're in a FIPS-enforced deployment (Argon2id is not FIPS-approved; see COMPLIANCE.md). For everything else, Argon2id is the modern best answer.

Performance characteristics

The numbers below are starting points on typical 2024 server hardware โ€” measure on yours before you ship. See OPERATIONS.md for the full envelope, capacity model, and monitoring guidance.

Parameter set Per-hash time RAM per in-flight hash Recommended for
m = 19 456, t = 2 (OWASP minimum) ~30โ€“60 ms ~19 MiB Floor; not recommended
m = 65 536, t = 3 (library default) ~150โ€“250 ms ~64 MiB Consumer SaaS, general-purpose
m = 131 072, t = 4 ~400โ€“600 ms ~128 MiB Internal / B2B, latency-tolerant
m = 262 144, t = 5 ~800 ms โ€“ 1.2 s ~256 MiB High-value (banking, healthcare)

Verification time tracks hash time within ~5%. The library emits metrics under Argon2idDiagnostics.MeterName so you can chart this directly in your observability stack:

builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m.AddMeter(Argon2idDiagnostics.MeterName));

Trim and Native AOT publish is supported; both packages are marked IsTrimmable and IsAotCompatible.

Security posture

Concern How this library handles it
GPU / ASIC cracking Argon2id, memory-hard (64 MiB default)
Rainbow tables 128-bit cryptographically random salt per hash (RandomNumberGenerator)
Parameter drift Parameters embedded in the PHC string + NeedsRehash
Timing side channels FixedTimeEquals on the final comparison
Sensitive memory Password, salt, and candidate hash buffers zeroed with CryptographicOperations.ZeroMemory; Span overloads avoid string
Database-only leak Optional keyed pepper (secret kept outside the DB), with rotation
Algorithm confusion Verifier accepts only argon2id, version 19
Insecure config Below-minimum parameters throw at construction
Supply chain SourceLink, deterministic builds, NuGetAudit at build time, CodeQL, build-provenance attestations on every release

This library is one layer. It does not provide rate limiting, account lockout, breached-password checks, or MFA โ€” those belong at your application/identity layer. For a frank account of everything it does not do (plaintext string lifetime, memory-cost DoS, and more), read KNOWN-GAPS.md. Transparency is a feature.

What we claim โ€” and what we don't

So you can calibrate trust precisely, here is the claim boundary in one place:

We claim: correct Argon2id per RFC 9106 โ€” continuously verified by a known-answer-vector corpus cross-checked against the reference C implementation, differential testing against an independent second implementation, coverage-guided fuzzing of the parser, property-based tests, and mutation testing of the test suite itself. Reproducible builds, provenance attestations, and SBOMs let you verify that what's on NuGet is what's in this repo.

We do not claim: an independent third-party cryptographic audit (KNOWN-GAPS.md ยง11 โ€” none has been funded yet; the code is structured and documented to make one cheap when it becomes possible), FIPS validation (COMPLIANCE.md), or a vendor support contract (SUPPORT.md). Until an audit happens, treat this as a high-quality open-source dependency โ€” verify our work via the threat model, the test suite, and the reproducible builds, not our say-so.

API reference

Argon2idPasswordHasher

Argon2idPasswordHasher()                                          // recommended defaults, no pepper
Argon2idPasswordHasher(Argon2idOptions options)                   // custom parameters
Argon2idPasswordHasher(Argon2idOptions options, PepperRing? pepper)

string HashPassword(string password)
string HashPassword(ReadOnlySpan<char> password)
string HashPassword(ReadOnlySpan<byte> password)

bool   VerifyPassword(string password, string encodedHash)
bool   VerifyPassword(ReadOnlySpan<char> password, string encodedHash)
bool   VerifyPassword(ReadOnlySpan<byte> password, string encodedHash)

VerifyResult Verify(string password, string encodedHash)           // single-parse: returns Success + NeedsRehash
VerifyResult Verify(ReadOnlySpan<char> password, string encodedHash)
VerifyResult Verify(ReadOnlySpan<byte> password, string encodedHash)

bool   NeedsRehash(string encodedHash)

Argon2idOptions Options { get; }

VerifyResult (readonly record struct) โ€” Success, NeedsRehash, static Failed. Returned by Verify(...).

Argon2idOptions (record) โ€” MemorySizeKib, Iterations, DegreeOfParallelism, SaltSizeBytes, HashSizeBytes, Validate(), static Recommended.

Pepper โ€” Pepper(string id, byte[] key), string Id. PepperRing โ€” PepperRing(Pepper active, params Pepper[] retired), Pepper Active.

Argon2id.PasswordHasher.AspNetCore โ€” Argon2idPasswordHasher<TUser> : IPasswordHasher<TUser> and IServiceCollection.AddArgon2idPasswordHasher<TUser>(options?, pepper?).

The full public surface is locked by Microsoft.CodeAnalysis.PublicApiAnalyzers; see PublicAPI.Shipped.txt / PublicAPI.Unshipped.txt next to each csproj.

FAQ

Which Argon2 variant? Argon2id only โ€” RFC 9106's recommended variant. Argon2i/Argon2d are intentionally not offered, and the verifier rejects them.

Are hashes interoperable with other Argon2 libraries? Yes for the standard form โ€” it's the same PHC string libsodium and the reference implementation use. The optional keyid parameter is a PHC extension some parsers may not expect.

Do I need to store the salt separately? No. The salt (and all parameters) are part of the returned string. Store that single value.

How slow should hashing be? Aim for roughly 100โ€“500 ms per hash on your production hardware for interactive logins. Benchmark and tune โ€” see docs/parameter-tuning.md.

Can I hash API keys / tokens with this? It's designed for human passwords. High-entropy secrets don't need memory-hard hashing; a fast keyed hash (HMAC/SHA-256) is usually the right tool.

Building, testing & benchmarks

dotnet build -c Release
dotnet test  -c Release

# Run the benchmarks (Release only)
dotnet run -c Release --project benchmarks/Argon2id.PasswordHasher.Benchmarks

# Run the Blazor demo
dotnet run --project samples/Argon2id.PasswordHasher.Demo

Repository layout:

src/Argon2id.PasswordHasher/              core library
src/Argon2id.PasswordHasher.AspNetCore/   IPasswordHasher<TUser> adapter + DI
tests/                                     xUnit test projects
benchmarks/                                BenchmarkDotNet harness
samples/Argon2id.PasswordHasher.Demo/      runnable Blazor Server demo (hardened)
samples/Argon2id.PasswordHasher.WasmDemo/  Blazor WebAssembly demo (deployed to GH Pages)
docs/                                      tuning & design notes
.github/                                   CI, CodeQL, Dependabot, templates

CI builds and tests on every push/PR across Ubuntu, Windows, and macOS for all three TFMs. CodeQL scans run weekly. Tagged releases (v*) pack both packages, generate CycloneDX SBOMs, issue build-provenance attestations, and create a GitHub Release with all artifacts attached. NuGet publication is a separate manual CLI step โ€” see PUBLISHING.md.

Versioning & status

1.0.0 โ€” stable. The public API surface and the PHC hash format are frozen under SemVer: breaking changes only in a major version with a migration guide, and every hash emitted by a 1.x release verifies against every later 1.x release. The SUPPORT.md supported-versions policy is in effect.

1.0.0 is a stability commitment, not an audit or assurance claim โ€” see KNOWN-GAPS.md ยง9 for the precise boundary, which was stated before the release rather than after.

See CHANGELOG.md for the full version history.

Contributing & security

Issues and PRs are welcome โ€” see CONTRIBUTING.md and CODE_OF_CONDUCT.md. Security-relevant changes should update SECURITY.md and/or KNOWN-GAPS.md.

Found a vulnerability? Please report it privately โ€” see SECURITY.md. Do not open public issues for security reports.

License & acknowledgements

MIT ยฉ Paul Clark. See THIRD-PARTY-NOTICES.md for upstream attributions.

Built on Konscious.Security.Cryptography.Argon2, an MIT-licensed managed Argon2 implementation. We chose a managed port over the reference C / libsodium so the package ships with no P/Invoke surface and runs unchanged under trimming, Native AOT, and Blazor WebAssembly; that trade-off and how we mitigate it (version pin, NuGetAudit, Dependabot, a pinned KAT cross-checked against the reference implementation) are documented in SECURITY.md and KNOWN-GAPS.md ยง12. Parameter guidance follows RFC 9106 and the OWASP Password Storage Cheat Sheet.


<div align="center">

To God be the glory โ€” 1 Corinthians 10:31.

</div>

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 is compatible.  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 is compatible.  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 Argon2id.PasswordHasher:

Package Downloads
Argon2id.PasswordHasher.AspNetCore

ASP.NET Core Identity integration for Argon2id.PasswordHasher. Provides an IPasswordHasher<TUser> implementation backed by secure-by-default Argon2id, with automatic rehash-on-login (PasswordVerificationResult.SuccessRehashNeeded) and a one-line AddArgon2idPasswordHasher DI extension. Targets .NET 8, 9, and 10.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 131 7/2/2026
0.4.0-preview.5 185 6/11/2026
0.4.0-preview.4 207 6/2/2026
0.4.0-preview.3 63 5/31/2026
0.4.0-preview.2 63 5/30/2026