{"id":50486815,"url":"https://github.com/systemslibrarian/argon2id-passwordhasher","last_synced_at":"2026-06-01T23:02:31.938Z","repository":{"id":361457021,"uuid":"1254514787","full_name":"systemslibrarian/argon2id-passwordhasher","owner":"systemslibrarian","description":"Modern, opinionated Argon2id password hashing library for .NET 10 with secure defaults, high performance, and excellent documentation.","archived":false,"fork":false,"pushed_at":"2026-05-30T19:10:29.000Z","size":34,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T19:15:03.253Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemslibrarian.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T17:00:29.000Z","updated_at":"2026-05-30T19:10:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/systemslibrarian/argon2id-passwordhasher","commit_stats":null,"previous_names":["systemslibrarian/argon2id-passwordhasher"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/systemslibrarian/argon2id-passwordhasher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fargon2id-passwordhasher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fargon2id-passwordhasher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fargon2id-passwordhasher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fargon2id-passwordhasher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemslibrarian","download_url":"https://codeload.github.com/systemslibrarian/argon2id-passwordhasher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fargon2id-passwordhasher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33797128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-01T23:02:31.004Z","updated_at":"2026-06-01T23:02:31.931Z","avatar_url":"https://github.com/systemslibrarian.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🔐 Argon2id.PasswordHasher\n\n**The opinionated, secure-by-default Argon2id password hasher for .NET 8, 9, and 10.**\n\n[![CI](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/codeql.yml)\n[![Pages](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/pages.yml/badge.svg?branch=main)](https://systemslibrarian.github.io/argon2id-passwordhasher/)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/systemslibrarian/argon2id-passwordhasher/badge)](https://securityscorecards.dev/viewer/?uri=github.com/systemslibrarian/argon2id-passwordhasher)\n[![NuGet](https://img.shields.io/nuget/vpre/Argon2id.PasswordHasher.svg?logo=nuget\u0026label=Argon2id.PasswordHasher)](https://www.nuget.org/packages/Argon2id.PasswordHasher)\n[![NuGet (AspNetCore)](https://img.shields.io/nuget/vpre/Argon2id.PasswordHasher.AspNetCore.svg?logo=nuget\u0026label=.AspNetCore)](https://www.nuget.org/packages/Argon2id.PasswordHasher.AspNetCore)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4.svg)](https://dotnet.microsoft.com/)\n[![AOT](https://img.shields.io/badge/AOT-compatible-success.svg)](#)\n\n*Memory-hard password hashing that's hard to get wrong.*\n\n\u003c/div\u003e\n\n---\n\nArgon2id.PasswordHasher wraps a vetted Argon2id implementation in a tiny, hard-to-misuse API\nwith **strong defaults out of the box**. You get memory-hard resistance to GPU/ASIC cracking\n(RFC 9106 / OWASP-aligned), self-describing hashes that carry their own parameters, constant-time\nverification, an optional keyed **pepper** with rotation, and a one-line ASP.NET Core Identity\nintegration — without having to become a cryptographer first.\n\n```csharp\nusing Argon2id.PasswordHasher;\n\nvar hasher = new Argon2idPasswordHasher();\n\nstring stored = hasher.HashPassword(\"correct horse battery staple\");\n//   $argon2id$v=19$m=65536,t=3,p=1$\u003csalt\u003e$\u003chash\u003e\n\nbool ok = hasher.VerifyPassword(\"correct horse battery staple\", stored); // true\n```\n\n## Try the demo\n\nThere are two runnable samples, sharing the same UX so you can pick whichever\nfits what you want to show:\n\n| Sample | Where it runs | When to use it |\n| --- | --- | --- |\n| [**Live WASM demo**](https://systemslibrarian.github.io/argon2id-passwordhasher/) | 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. |\n| [**API documentation**](https://systemslibrarian.github.io/argon2id-passwordhasher/docs/) | DocFx, browser | Every public type + member, generated from XML doc comments. Co-published with the live demo. |\n| [`samples/Argon2id.PasswordHasher.Demo`](samples/Argon2id.PasswordHasher.Demo) | Blazor Server (local) | Shows production-shape integration: DI, antiforgery, rate limiting, HSTS, CSP, constant-time login, memory-cost DoS gate. |\n| [`samples/Argon2id.PasswordHasher.WasmDemo`](samples/Argon2id.PasswordHasher.WasmDemo) | Blazor WebAssembly (local) | Same UX as the live demo, but running against the in-tree library. Edit and refresh. |\n\nRun either locally:\n\n```bash\ngit clone https://github.com/systemslibrarian/argon2id-passwordhasher.git\ncd argon2id-passwordhasher\n\n# Server flavor (production-shape, hardened):\ndotnet run --project samples/Argon2id.PasswordHasher.Demo\n\n# WASM flavor (same UX as the live demo):\ndotnet run --project samples/Argon2id.PasswordHasher.WasmDemo\n```\n\nBoth samples use a `ProjectReference` to the in-tree library, so they always\nexercise the version you're working on. **They are demos, not starter\ntemplates** — users live in memory and hash internals are shown on screen for\neducational clarity (see each sample's own README).\n\n## Documentation\n\nDocumentation is layered so you only read the depth you need:\n\n| Audience / question | Doc |\n| --- | --- |\n| \"How do I use it?\" | This README, plus [API reference](https://systemslibrarian.github.io/argon2id-passwordhasher/docs/) |\n| \"How do I migrate from Identity's default PasswordHasher?\" | [`MIGRATION.md`](MIGRATION.md) |\n| \"How should I run this in production?\" | [`OPERATIONS.md`](OPERATIONS.md) — capacity planning, monitoring, alerting, failure modes |\n| \"What about FIPS / SOC 2 / vendor questionnaires?\" | [`COMPLIANCE.md`](COMPLIANCE.md) |\n| \"What is the security threat model?\" | [`THREAT-MODEL.md`](THREAT-MODEL.md) |\n| \"How do I store the pepper in Azure Key Vault / AWS / Vault?\" | [`docs/pepper-key-management.md`](docs/pepper-key-management.md) |\n| \"What's the support and lifecycle policy?\" | [`SUPPORT.md`](SUPPORT.md) |\n| \"How do I tune the work factor?\" | [`docs/parameter-tuning.md`](docs/parameter-tuning.md) |\n| \"What does the library deliberately NOT do?\" | [`KNOWN-GAPS.md`](KNOWN-GAPS.md) |\n| \"How do I report a vulnerability?\" | [`SECURITY.md`](SECURITY.md) |\n| \"How do I contribute?\" | [`CONTRIBUTING.md`](CONTRIBUTING.md) |\n| \"What changed in this version?\" | [`CHANGELOG.md`](CHANGELOG.md) |\n\n## Table of contents\n\n- [Why this library](#why-this-library)\n- [Packages](#packages)\n- [Install](#install)\n- [Quick start](#quick-start)\n- [The hash format](#the-hash-format)\n- [Configuration](#configuration)\n- [Upgrading work factor (rehash on login)](#upgrading-work-factor-rehash-on-login)\n- [Avoiding `string` passwords (span overloads)](#avoiding-string-passwords-span-overloads)\n- [Pepper (secret key) with rotation](#pepper-secret-key-with-rotation)\n- [ASP.NET Core Identity](#aspnet-core-identity)\n- [Trimming \u0026 Native AOT](#trimming--native-aot)\n- [Security posture](#security-posture)\n- [API reference](#api-reference)\n- [FAQ](#faq)\n- [Building, testing \u0026 benchmarks](#building-testing--benchmarks)\n- [Versioning \u0026 status](#versioning--status)\n- [Contributing \u0026 security](#contributing--security)\n- [License \u0026 acknowledgements](#license--acknowledgements)\n\n## Why this library\n\n| | |\n| --- | --- |\n| 🛡️ **Secure by default** | The no-arg constructor gives you 64 MiB / t=3 / p=1 — comfortably above the OWASP minimum. No footguns to configure. |\n| 📦 **Self-describing hashes** | Every hash is a standard **PHC string** containing its own parameters, so verification never breaks when you raise the work factor. |\n| ♻️ **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. |\n| ⏱️ **Constant-time** | Final comparison uses `CryptographicOperations.FixedTimeEquals`. |\n| 🌶️ **Pepper with rotation** | Optional keyed secret kept outside your database, with first-class key rotation. |\n| 🧩 **ASP.NET Core ready** | Drop-in `IPasswordHasher\u003cTUser\u003e` + a one-line DI extension. |\n| 🚀 **Trim + AOT compatible** | Marked `IsTrimmable` and `IsAotCompatible` so Native AOT consumers just work. |\n| 🧼 **Tiny \u0026 honest** | One runtime dependency. Every real limitation is documented in [`KNOWN-GAPS.md`](KNOWN-GAPS.md). |\n\n## Packages\n\n| Package | Purpose | Depends on |\n| --- | --- | --- |\n| [`Argon2id.PasswordHasher`](https://www.nuget.org/packages/Argon2id.PasswordHasher) | Core hasher — no web/framework dependency | Konscious.Security.Cryptography.Argon2 |\n| [`Argon2id.PasswordHasher.AspNetCore`](https://www.nuget.org/packages/Argon2id.PasswordHasher.AspNetCore) | `IPasswordHasher\u003cTUser\u003e` adapter + DI extension | the core package + Microsoft.Extensions.Identity.Core |\n\nBoth packages target **`net8.0`, `net9.0`, and `net10.0`**.\n\n## Install\n\n```bash\n# Core\ndotnet add package Argon2id.PasswordHasher --prerelease\n\n# Optional: ASP.NET Core Identity integration\ndotnet add package Argon2id.PasswordHasher.AspNetCore --prerelease\n```\n\n## Quick start\n\n**Registration** — hash the password and store the returned string:\n\n```csharp\nvar hasher = new Argon2idPasswordHasher();\nuser.PasswordHash = hasher.HashPassword(password);\nawait db.SaveChangesAsync();\n```\n\n**Login** — verify against the stored string:\n\n```csharp\nif (!hasher.VerifyPassword(password, user.PasswordHash))\n    return Unauthorized();\n\n// Optionally strengthen the stored hash if your settings have grown:\nif (hasher.NeedsRehash(user.PasswordHash))\n{\n    user.PasswordHash = hasher.HashPassword(password);\n    await db.SaveChangesAsync();\n}\n```\n\n`VerifyPassword` **never throws** on bad input: a `null`, empty, malformed, or non-Argon2id\nstored value simply returns `false`.\n\n\u003e [!TIP]\n\u003e `Argon2idPasswordHasher` is stateless and thread-safe. Create one and reuse it (e.g. register\n\u003e it as a singleton) rather than constructing one per request.\n\n## The hash format\n\nHashes are emitted in the standard **PHC string format** used by libsodium and the Argon2\nreference implementation:\n\n```\n$argon2id$v=19$m=65536,t=3,p=1$\u003cbase64 salt\u003e$\u003cbase64 hash\u003e\n└── alg ──┘└ ver ┘└── cost params ──┘└── salt ──┘└── hash ──┘\n```\n\nBecause the parameters live *inside* the string, the hash is **portable** and **self-verifying**:\nyou can change your defaults whenever you like and old hashes keep validating with the parameters\nthey were created with. When a pepper is used, an extra `keyid=\u003cid\u003e` parameter is added so\nverification can select the right secret.\n\n## Configuration\n\nPass an `Argon2idOptions` to tune the work factor. Invalid values (below the safe minimums) throw\n`ArgumentOutOfRangeException` at construction — fail fast, not silently weak.\n\n```csharp\nvar hasher = new Argon2idPasswordHasher(new Argon2idOptions\n{\n    MemorySizeKib       = 131072, // 128 MiB\n    Iterations          = 4,\n    DegreeOfParallelism = 1,\n});\n```\n\n| Option | Default | Minimum | Notes |\n| --- | --- | --- | --- |\n| `MemorySizeKib` | `65536` (64 MiB) | `8192` | Primary GPU/ASIC defense. Raise this first. |\n| `Iterations` | `3` | `1` | Passes over memory (linear CPU cost). |\n| `DegreeOfParallelism` | `1` | `1` | Lanes/threads per hash. Keep low on shared servers. |\n| `SaltSizeBytes` | `16` (128-bit) | `16` | RFC 9106 recommendation. |\n| `HashSizeBytes` | `32` (256-bit) | `16` | Output (tag) length. |\n\n**Why these defaults?** They're a strong, general-purpose baseline for 2026 server hardware that\nexceeds the current OWASP minimum (Argon2id, 19 MiB, t=2, p=1) while keeping `p=1` so per-hash CPU\nstays predictable under concurrent logins. They are **not** tuned to *your* machine — measure and\nadjust. See [`docs/parameter-tuning.md`](docs/parameter-tuning.md) and the\n[benchmark project](benchmarks/Argon2id.PasswordHasher.Benchmarks).\n\n`Argon2idOptions.Recommended` exposes the defaults explicitly.\n\n## Upgrading work factor (rehash on login)\n\nSecurity guidance gets stronger over time. When you raise your parameters, existing users upgrade\nthemselves the next time they sign in — no mass migration, no broken logins:\n\n```csharp\nif (hasher.VerifyPassword(password, user.PasswordHash))\n{\n    if (hasher.NeedsRehash(user.PasswordHash))\n        user.PasswordHash = hasher.HashPassword(password); // re-hashed with current settings\n    // sign the user in...\n}\n```\n\n`NeedsRehash` returns `true` when the stored hash is unparseable, any stored parameter is below\nyour current configuration, or (with a pepper ring) the hash doesn't use your active pepper.\n\n## Avoiding `string` passwords (span overloads)\n\n`HashPassword` / `VerifyPassword` also accept `ReadOnlySpan\u003cchar\u003e` and `ReadOnlySpan\u003cbyte\u003e`, so you\ncan hash a credential without ever materializing it as a `string`. The hasher zeroes every\npassword-derived buffer it owns.\n\n```csharp\nReadOnlySpan\u003cchar\u003e pw = GetPasswordChars();\nstring hash = hasher.HashPassword(pw);\nbool ok    = hasher.VerifyPassword(pw, hash);\n```\n\n\u003e [!NOTE]\n\u003e .NET cannot reliably wipe an immutable `string` from memory. Prefer the span overloads when the\n\u003e password doesn't otherwise need to be a `string`. See [`KNOWN-GAPS.md`](KNOWN-GAPS.md) §1.\n\n## Pepper (secret key) with rotation\n\nA **pepper** is an application secret mixed into every hash and kept **outside** the database (in a\nkey vault, KMS, or environment variable). If your password table leaks but the pepper doesn't, the\nstolen hashes can't be cracked offline. Peppers here are **keyed and rotatable** — each hash records\n*which* pepper produced it (via the PHC `keyid`), and the key bytes are never stored.\n\n```csharp\nbyte[] key = GetPepperFromVault();             // ≥ 16 bytes, kept secret\nvar ring   = new PepperRing(new Pepper(\"2026-05\", key));\nvar hasher = new Argon2idPasswordHasher(Argon2idOptions.Recommended, ring);\n\nstring hash = hasher.HashPassword(password);\n//   $argon2id$v=19$m=65536,t=3,p=1,keyid=\u003cid\u003e$\u003csalt\u003e$\u003chash\u003e\n```\n\n**Rotation** — promote a new active key and keep the old one as *retired* so existing hashes still\nverify; `NeedsRehash` then upgrades them on the next login:\n\n```csharp\nvar rotated = new PepperRing(\n    active:  new Pepper(\"2026-11\", newKey),\n    retired: new Pepper(\"2026-05\", oldKey));\n```\n\n\u003e [!WARNING]\n\u003e The library never persists pepper keys — that's your responsibility. **Lose the active key and\n\u003e you lose the ability to verify hashes made with it.** Back up and retire keys deliberately.\n\nFor end-to-end examples of loading peppers from **Azure Key Vault, AWS Secrets Manager,\nGoogle Cloud Secret Manager, HashiCorp Vault**, or environment variables, plus the full\nrotation playbook, see [`docs/pepper-key-management.md`](docs/pepper-key-management.md).\n\n## ASP.NET Core Identity\n\nInstall `Argon2id.PasswordHasher.AspNetCore` and register the hasher in one line:\n\n```csharp\nbuilder.Services\n    .AddIdentityCore\u003cIdentityUser\u003e()\n    .Services\n    .AddArgon2idPasswordHasher\u003cIdentityUser\u003e(); // optional: pass Argon2idOptions and/or a PepperRing\n```\n\nThis registers an `IPasswordHasher\u003cTUser\u003e` backed by Argon2id and shares a single core hasher as a\nsingleton. Verification maps cleanly onto Identity's contract:\n\n| Result | When |\n| --- | --- |\n| `Success` | Password matches and the hash is up to date. |\n| `SuccessRehashNeeded` | Password matches but the hash is weaker than current settings / uses an old pepper. Identity rehashes it automatically. |\n| `Failed` | Password doesn't match, or the stored value is malformed. |\n\n## Trimming \u0026 Native AOT\n\nBoth packages are marked `IsTrimmable=true` and `IsAotCompatible=true`. No reflection, no dynamic\ncodegen, no `System.Reflection.Emit` — the library uses only BCL crypto primitives plus the\nKonscious managed Argon2 implementation. Native AOT consumers can publish trimmed binaries without\nwarnings.\n\n## How this library compares\n\nQuick orientation for the common alternatives in the .NET ecosystem:\n\n| Library | Algorithm | Memory-hard | Self-describing hash | First-class pepper | ASP.NET Identity adapter | TFMs |\n| --- | --- | --- | --- | --- | --- | --- |\n| **`Argon2id.PasswordHasher`** (this library) | **Argon2id (RFC 9106)** | ✅ | ✅ (PHC) | ✅ + rotation | ✅ + migration adapter | net8 / net9 / net10 |\n| `Microsoft.AspNetCore.Identity.PasswordHasher\u003cTUser\u003e` (the default) | PBKDF2 HMAC-SHA-512, 100k iterations | ❌ | semi (version byte prefix) | ❌ | n/a (it *is* the default) | every .NET they support |\n| `Konscious.Security.Cryptography.Argon2` (raw) | Argon2i / Argon2d / Argon2id | ✅ | ❌ (raw bytes) | manual | ❌ | netstandard2.0 |\n| `BCrypt.Net-Next` | bcrypt | ❌ | ✅ (`$2a$…`) | ❌ | community wrappers | netstandard2.0 |\n| `Isopoh.Cryptography.Argon2` | Argon2i / Argon2d / Argon2id | ✅ | ✅ (encoded) | ❌ | ❌ | netstandard2.0 |\n\nWhen this library is the right choice: you want **Argon2id specifically**\n(per OWASP's current top recommendation for password storage), with\n**self-describing PHC strings** so you can raise the work factor without\nbreaking existing users, **optional first-class pepper with rotation**,\nand a **drop-in `IPasswordHasher\u003cTUser\u003e`** so an existing ASP.NET Core\nIdentity app can migrate with one line.\n\nWhen the default `PasswordHasher\u003cTUser\u003e` is the right choice: you're in\na FIPS-enforced deployment (Argon2id is not FIPS-approved; see\n[`COMPLIANCE.md`](COMPLIANCE.md)). For everything else, Argon2id is the\nmodern best answer.\n\n## Performance characteristics\n\nThe numbers below are **starting points on typical 2024 server hardware**\n— measure on yours before you ship. See\n[`OPERATIONS.md`](OPERATIONS.md) for the full envelope, capacity model,\nand monitoring guidance.\n\n| Parameter set | Per-hash time | RAM per in-flight hash | Recommended for |\n| --- | --- | --- | --- |\n| `m = 19 456`, `t = 2` (OWASP minimum) | ~30–60 ms | ~19 MiB | Floor; not recommended |\n| `m = 65 536`, `t = 3` (library default) | ~150–250 ms | ~64 MiB | Consumer SaaS, general-purpose |\n| `m = 131 072`, `t = 4` | ~400–600 ms | ~128 MiB | Internal / B2B, latency-tolerant |\n| `m = 262 144`, `t = 5` | ~800 ms – 1.2 s | ~256 MiB | High-value (banking, healthcare) |\n\nVerification time tracks hash time within ~5%. The library emits\nmetrics under\n[`Argon2idDiagnostics.MeterName`](src/Argon2id.PasswordHasher/Argon2idDiagnostics.cs)\nso you can chart this directly in your observability stack:\n\n```csharp\nbuilder.Services.AddOpenTelemetry()\n    .WithMetrics(m =\u003e m.AddMeter(Argon2idDiagnostics.MeterName));\n```\n\nTrim and Native AOT publish is supported; both packages are marked\n`IsTrimmable` and `IsAotCompatible`.\n\n## Security posture\n\n| Concern | How this library handles it |\n| --- | --- |\n| GPU / ASIC cracking | Argon2id, memory-hard (64 MiB default) |\n| Rainbow tables | 128-bit cryptographically random salt per hash (`RandomNumberGenerator`) |\n| Parameter drift | Parameters embedded in the PHC string + `NeedsRehash` |\n| Timing side channels | `FixedTimeEquals` on the final comparison |\n| Sensitive memory | Password, salt, and candidate hash buffers zeroed with `CryptographicOperations.ZeroMemory`; `Span` overloads avoid `string` |\n| Database-only leak | Optional keyed **pepper** (secret kept outside the DB), with rotation |\n| Algorithm confusion | Verifier accepts only `argon2id`, version 19 |\n| Insecure config | Below-minimum parameters throw at construction |\n| Supply chain | SourceLink, deterministic builds, `NuGetAudit` at build time, CodeQL, build-provenance attestations on every release |\n\n**This library is one layer.** It does not provide rate limiting, account lockout, breached-password\nchecks, or MFA — those belong at your application/identity layer. For a frank account of everything\nit does *not* do (plaintext `string` lifetime, memory-cost DoS, and more), read\n[`KNOWN-GAPS.md`](KNOWN-GAPS.md). Transparency is a feature.\n\n## API reference\n\n**`Argon2idPasswordHasher`**\n\n```csharp\nArgon2idPasswordHasher()                                          // recommended defaults, no pepper\nArgon2idPasswordHasher(Argon2idOptions options)                   // custom parameters\nArgon2idPasswordHasher(Argon2idOptions options, PepperRing? pepper)\n\nstring HashPassword(string password)\nstring HashPassword(ReadOnlySpan\u003cchar\u003e password)\nstring HashPassword(ReadOnlySpan\u003cbyte\u003e password)\n\nbool   VerifyPassword(string password, string encodedHash)\nbool   VerifyPassword(ReadOnlySpan\u003cchar\u003e password, string encodedHash)\nbool   VerifyPassword(ReadOnlySpan\u003cbyte\u003e password, string encodedHash)\n\nVerifyResult Verify(string password, string encodedHash)           // single-parse: returns Success + NeedsRehash\nVerifyResult Verify(ReadOnlySpan\u003cchar\u003e password, string encodedHash)\nVerifyResult Verify(ReadOnlySpan\u003cbyte\u003e password, string encodedHash)\n\nbool   NeedsRehash(string encodedHash)\n\nArgon2idOptions Options { get; }\n```\n\n**`VerifyResult`** (readonly record struct) — `Success`, `NeedsRehash`,\n`static Failed`. Returned by `Verify(...)`.\n\n**`Argon2idOptions`** (record) — `MemorySizeKib`, `Iterations`, `DegreeOfParallelism`,\n`SaltSizeBytes`, `HashSizeBytes`, `Validate()`, `static Recommended`.\n\n**`Pepper`** — `Pepper(string id, byte[] key)`, `string Id`.\n**`PepperRing`** — `PepperRing(Pepper active, params Pepper[] retired)`, `Pepper Active`.\n\n**`Argon2id.PasswordHasher.AspNetCore`** — `Argon2idPasswordHasher\u003cTUser\u003e : IPasswordHasher\u003cTUser\u003e`\nand `IServiceCollection.AddArgon2idPasswordHasher\u003cTUser\u003e(options?, pepper?)`.\n\nThe full public surface is locked by [`Microsoft.CodeAnalysis.PublicApiAnalyzers`](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md);\nsee `PublicAPI.Shipped.txt` / `PublicAPI.Unshipped.txt` next to each csproj.\n\n## FAQ\n\n**Which Argon2 variant?** Argon2id only — RFC 9106's recommended variant. Argon2i/Argon2d are\nintentionally not offered, and the verifier rejects them.\n\n**Are hashes interoperable with other Argon2 libraries?** Yes for the standard form — it's the same\nPHC string libsodium and the reference implementation use. The optional `keyid` parameter is a PHC\nextension some parsers may not expect.\n\n**Do I need to store the salt separately?** No. The salt (and all parameters) are part of the\nreturned string. Store that single value.\n\n**How slow should hashing be?** Aim for roughly 100–500 ms per hash on your production hardware for\ninteractive logins. Benchmark and tune — see [`docs/parameter-tuning.md`](docs/parameter-tuning.md).\n\n**Can I hash API keys / tokens with this?** It's designed for *human* passwords. High-entropy\nsecrets don't need memory-hard hashing; a fast keyed hash (HMAC/SHA-256) is usually the right tool.\n\n## Building, testing \u0026 benchmarks\n\n```bash\ndotnet build -c Release\ndotnet test  -c Release\n\n# Run the benchmarks (Release only)\ndotnet run -c Release --project benchmarks/Argon2id.PasswordHasher.Benchmarks\n\n# Run the Blazor demo\ndotnet run --project samples/Argon2id.PasswordHasher.Demo\n```\n\nRepository layout:\n\n```\nsrc/Argon2id.PasswordHasher/              core library\nsrc/Argon2id.PasswordHasher.AspNetCore/   IPasswordHasher\u003cTUser\u003e adapter + DI\ntests/                                     xUnit test projects\nbenchmarks/                                BenchmarkDotNet harness\nsamples/Argon2id.PasswordHasher.Demo/      runnable Blazor Server demo (hardened)\nsamples/Argon2id.PasswordHasher.WasmDemo/  Blazor WebAssembly demo (deployed to GH Pages)\ndocs/                                      tuning \u0026 design notes\n.github/                                   CI, CodeQL, Dependabot, templates\n```\n\nCI builds and tests on every push/PR across Ubuntu, Windows, and macOS for all three TFMs.\nCodeQL scans run weekly. Tagged releases (`v*`) pack both packages, generate CycloneDX SBOMs,\nissue build-provenance attestations, and create a GitHub Release with all artifacts attached.\n**NuGet publication is a separate manual CLI step** — see [`PUBLISHING.md`](PUBLISHING.md).\n\n## Versioning \u0026 status\n\n`0.4.0-preview.3` — **preview**. Follows SemVer with preview suffixes. The API and defaults may\nstill change before `1.0.0`; hashes use the standard PHC format and are expected to stay verifiable.\n\nSee [`CHANGELOG.md`](CHANGELOG.md) for the full version history.\n\n## Contributing \u0026 security\n\nIssues and PRs are welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md) and\n[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Security-relevant changes should update\n[`SECURITY.md`](SECURITY.md) and/or [`KNOWN-GAPS.md`](KNOWN-GAPS.md).\n\n**Found a vulnerability?** Please report it privately — see [`SECURITY.md`](SECURITY.md). Do not\nopen public issues for security reports.\n\n## License \u0026 acknowledgements\n\n[MIT](LICENSE) © Paul Clark. See [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md) for upstream\nattributions.\n\nBuilt on [Konscious.Security.Cryptography.Argon2](https://github.com/kmaragon/Konscious.Security.Cryptography),\nan MIT-licensed managed Argon2 implementation. Parameter guidance follows\n[RFC 9106](https://www.rfc-editor.org/rfc/rfc9106) and the\n[OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n*To God be the glory — 1 Corinthians 10:31.*\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fargon2id-passwordhasher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemslibrarian%2Fargon2id-passwordhasher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fargon2id-passwordhasher/lists"}