https://github.com/systemslibrarian/argon2id-passwordhasher
Modern, opinionated Argon2id password hashing library for .NET 10 with secure defaults, high performance, and excellent documentation.
https://github.com/systemslibrarian/argon2id-passwordhasher
Last synced: 1 day ago
JSON representation
Modern, opinionated Argon2id password hashing library for .NET 10 with secure defaults, high performance, and excellent documentation.
- Host: GitHub
- URL: https://github.com/systemslibrarian/argon2id-passwordhasher
- Owner: systemslibrarian
- License: mit
- Created: 2026-05-30T17:00:29.000Z (4 days ago)
- Default Branch: main
- Last Pushed: 2026-05-30T19:10:29.000Z (4 days ago)
- Last Synced: 2026-05-30T19:15:03.253Z (4 days ago)
- Language: C#
- Size: 33.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# ๐ Argon2id.PasswordHasher
**The opinionated, secure-by-default Argon2id password hasher for .NET 8, 9, and 10.**
[](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/ci.yml)
[](https://github.com/systemslibrarian/argon2id-passwordhasher/actions/workflows/codeql.yml)
[](https://systemslibrarian.github.io/argon2id-passwordhasher/)
[](https://securityscorecards.dev/viewer/?uri=github.com/systemslibrarian/argon2id-passwordhasher)
[](https://www.nuget.org/packages/Argon2id.PasswordHasher)
[](https://www.nuget.org/packages/Argon2id.PasswordHasher.AspNetCore)
[](LICENSE)
[](https://dotnet.microsoft.com/)
[](#)
*Memory-hard password hashing that's hard to get wrong.*
---
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.
```csharp
using Argon2id.PasswordHasher;
var hasher = new Argon2idPasswordHasher();
string stored = hasher.HashPassword("correct horse battery staple");
// $argon2id$v=19$m=65536,t=3,p=1$$
bool ok = hasher.VerifyPassword("correct horse battery staple", stored); // true
```
## Try the demo
There are two runnable samples, sharing the same UX so you can pick whichever
fits what you want to show:
| Sample | Where it runs | When to use it |
| --- | --- | --- |
| [**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. |
| [**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. |
| [`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. |
| [`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. |
Run either locally:
```bash
git clone https://github.com/systemslibrarian/argon2id-passwordhasher.git
cd argon2id-passwordhasher
# 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
```
Both 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](https://systemslibrarian.github.io/argon2id-passwordhasher/docs/) |
| "How do I migrate from Identity's default PasswordHasher?" | [`MIGRATION.md`](MIGRATION.md) |
| "How should I run this in production?" | [`OPERATIONS.md`](OPERATIONS.md) โ capacity planning, monitoring, alerting, failure modes |
| "What about FIPS / SOC 2 / vendor questionnaires?" | [`COMPLIANCE.md`](COMPLIANCE.md) |
| "What is the security threat model?" | [`THREAT-MODEL.md`](THREAT-MODEL.md) |
| "How do I store the pepper in Azure Key Vault / AWS / Vault?" | [`docs/pepper-key-management.md`](docs/pepper-key-management.md) |
| "What's the support and lifecycle policy?" | [`SUPPORT.md`](SUPPORT.md) |
| "How do I tune the work factor?" | [`docs/parameter-tuning.md`](docs/parameter-tuning.md) |
| "What does the library deliberately NOT do?" | [`KNOWN-GAPS.md`](KNOWN-GAPS.md) |
| "How do I report a vulnerability?" | [`SECURITY.md`](SECURITY.md) |
| "How do I contribute?" | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
| "What changed in this version?" | [`CHANGELOG.md`](CHANGELOG.md) |
## Table of contents
- [Why this library](#why-this-library)
- [Packages](#packages)
- [Install](#install)
- [Quick start](#quick-start)
- [The hash format](#the-hash-format)
- [Configuration](#configuration)
- [Upgrading work factor (rehash on login)](#upgrading-work-factor-rehash-on-login)
- [Avoiding `string` passwords (span overloads)](#avoiding-string-passwords-span-overloads)
- [Pepper (secret key) with rotation](#pepper-secret-key-with-rotation)
- [ASP.NET Core Identity](#aspnet-core-identity)
- [Trimming & Native AOT](#trimming--native-aot)
- [Security posture](#security-posture)
- [API reference](#api-reference)
- [FAQ](#faq)
- [Building, testing & benchmarks](#building-testing--benchmarks)
- [Versioning & status](#versioning--status)
- [Contributing & security](#contributing--security)
- [License & acknowledgements](#license--acknowledgements)
## 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` + 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`](KNOWN-GAPS.md). |
## Packages
| Package | Purpose | Depends on |
| --- | --- | --- |
| [`Argon2id.PasswordHasher`](https://www.nuget.org/packages/Argon2id.PasswordHasher) | Core hasher โ no web/framework dependency | Konscious.Security.Cryptography.Argon2 |
| [`Argon2id.PasswordHasher.AspNetCore`](https://www.nuget.org/packages/Argon2id.PasswordHasher.AspNetCore) | `IPasswordHasher` adapter + DI extension | the core package + Microsoft.Extensions.Identity.Core |
Both packages target **`net8.0`, `net9.0`, and `net10.0`**.
## Install
```bash
# 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:
```csharp
var hasher = new Argon2idPasswordHasher();
user.PasswordHash = hasher.HashPassword(password);
await db.SaveChangesAsync();
```
**Login** โ verify against the stored string:
```csharp
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`.
> [!TIP]
> `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$$
โโโ 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=` 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.
```csharp
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`](docs/parameter-tuning.md) and the
[benchmark project](benchmarks/Argon2id.PasswordHasher.Benchmarks).
`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:
```csharp
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` and `ReadOnlySpan`, so you
can hash a credential without ever materializing it as a `string`. The hasher zeroes every
password-derived buffer it owns.
```csharp
ReadOnlySpan pw = GetPasswordChars();
string hash = hasher.HashPassword(pw);
bool ok = hasher.VerifyPassword(pw, hash);
```
> [!NOTE]
> .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`](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.
```csharp
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=$$
```
**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:
```csharp
var rotated = new PepperRing(
active: new Pepper("2026-11", newKey),
retired: new Pepper("2026-05", oldKey));
```
> [!WARNING]
> The library never persists pepper keys โ that's your responsibility. **Lose the active key and
> you lose the ability to verify hashes made with it.** Back up and retire keys deliberately.
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`](docs/pepper-key-management.md).
## ASP.NET Core Identity
Install `Argon2id.PasswordHasher.AspNetCore` and register the hasher in one line:
```csharp
builder.Services
.AddIdentityCore()
.Services
.AddArgon2idPasswordHasher(); // optional: pass Argon2idOptions and/or a PepperRing
```
This registers an `IPasswordHasher` 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` (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`** so an existing ASP.NET Core
Identity app can migrate with one line.
When the default `PasswordHasher` is the right choice: you're in
a FIPS-enforced deployment (Argon2id is not FIPS-approved; see
[`COMPLIANCE.md`](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`](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`](src/Argon2id.PasswordHasher/Argon2idDiagnostics.cs)
so you can chart this directly in your observability stack:
```csharp
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`](KNOWN-GAPS.md). Transparency is a feature.
## API reference
**`Argon2idPasswordHasher`**
```csharp
Argon2idPasswordHasher() // recommended defaults, no pepper
Argon2idPasswordHasher(Argon2idOptions options) // custom parameters
Argon2idPasswordHasher(Argon2idOptions options, PepperRing? pepper)
string HashPassword(string password)
string HashPassword(ReadOnlySpan password)
string HashPassword(ReadOnlySpan password)
bool VerifyPassword(string password, string encodedHash)
bool VerifyPassword(ReadOnlySpan password, string encodedHash)
bool VerifyPassword(ReadOnlySpan password, string encodedHash)
VerifyResult Verify(string password, string encodedHash) // single-parse: returns Success + NeedsRehash
VerifyResult Verify(ReadOnlySpan password, string encodedHash)
VerifyResult Verify(ReadOnlySpan 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 : IPasswordHasher`
and `IServiceCollection.AddArgon2idPasswordHasher(options?, pepper?)`.
The full public surface is locked by [`Microsoft.CodeAnalysis.PublicApiAnalyzers`](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md);
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`](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
```bash
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 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`](PUBLISHING.md).
## Versioning & status
`0.4.0-preview.3` โ **preview**. Follows SemVer with preview suffixes. The API and defaults may
still change before `1.0.0`; hashes use the standard PHC format and are expected to stay verifiable.
See [`CHANGELOG.md`](CHANGELOG.md) for the full version history.
## Contributing & security
Issues and PRs are welcome โ see [`CONTRIBUTING.md`](CONTRIBUTING.md) and
[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Security-relevant changes should update
[`SECURITY.md`](SECURITY.md) and/or [`KNOWN-GAPS.md`](KNOWN-GAPS.md).
**Found a vulnerability?** Please report it privately โ see [`SECURITY.md`](SECURITY.md). Do not
open public issues for security reports.
## License & acknowledgements
[MIT](LICENSE) ยฉ Paul Clark. See [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md) for upstream
attributions.
Built on [Konscious.Security.Cryptography.Argon2](https://github.com/kmaragon/Konscious.Security.Cryptography),
an MIT-licensed managed Argon2 implementation. Parameter guidance follows
[RFC 9106](https://www.rfc-editor.org/rfc/rfc9106) and the
[OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
---
*To God be the glory โ 1 Corinthians 10:31.*