# Password hashing

## Purpose

[Argon2id](https://www.rfc-editor.org/rfc/rfc9106.html) is a memory-hard password hashing and password-based key derivation function (KDF). It takes the following parameters:

* A password.
* A 128-bit [random](/random-data.md#fill) salt.
* An iteration count.
* A memory size in bytes.

{% hint style="success" %}

1. Set the iteration count to 3.
2. Set the memory size as high as possible (minimum of 64 MiB) for a reasonable delay (e.g., 100 ms to 1 sec) on the type of device your application will run on.
3. If the delay is lower than you would like, increase the iterations.

See the [Notes](#notes) for some example parameters.
{% endhint %}

{% hint style="warning" %}
The same Unicode character can sometimes get encoded in multiple different ways depending on things like the OS/keyboard/device, which can prevent a user deriving the correct key/password hash every time. There are two solutions to this problem:

1. Only accept [ASCII](https://en.wikipedia.org/wiki/ASCII#Character_set) characters in passwords.
2. If non-ASCII characters are accepted in passwords, apply [Unicode Normalization Form C (NFC)](https://learn.microsoft.com/en-us/dotnet/api/system.string.normalize) to the password before password hashing. This is recommended by [NIST SP 800-63B-4](https://csrc.nist.gov/pubs/sp/800/63/b/4/final) and [RFC 8265](https://www.rfc-editor.org/rfc/rfc8265.html).

However, note that 2 does not actually fix this problem. There are still [rare cases](https://1passwordstatic.com/files/security/1password-white-paper.pdf) where Unicode characters can be problematic. Normalization also requires having a string copy of the password in memory, which is bad for secure erasure.
{% endhint %}

## Usage

### DeriveKey

Fills a span with output keying material computed from a password, a [random](/random-data.md#fill) salt, an iteration count, and a memory size in bytes.

```csharp
Argon2id.DeriveKey(Span<byte> outputKeyingMaterial, ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterations, int memorySize)
```

#### Exceptions

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`outputKeyingMaterial` has a length less than `MinKeySize`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`salt` has a length not equal to `SaltSize`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`iterations` is less than `MinIterations`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`memorySize` is less than `MinMemorySize`.

[InsufficientMemoryException](https://docs.microsoft.com/en-us/dotnet/api/system.insufficientmemoryexception)

Insufficient memory to perform key derivation.

### ComputeHash

Fills a span with an encoded password hash computed from a password, a randomly generated salt, an iteration count, and a memory size in bytes.

```csharp
Argon2id.ComputeHash(Span<char> hash, ReadOnlySpan<byte> password, int iterations, int memorySize)
```

{% hint style="success" %}
`hash` must be a fixed length due to libsodium's API, which pads the potentially variable-length output with null characters.

You can convert the hash into a string for storage in a database using [Span\<T>.ToString()](https://learn.microsoft.com/en-us/dotnet/api/system.span-1.tostring). Any null characters at the end can either be left alone or removed. Only this hash needs to be stored as the cost parameters and salt are encoded.
{% endhint %}

#### Exceptions

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`hash` has a length not equal to `HashSize`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`iterations` is less than `MinIterations`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`memorySize` is less than `MinMemorySize`.

[InsufficientMemoryException](https://docs.microsoft.com/en-us/dotnet/api/system.insufficientmemoryexception)

Insufficient memory to perform password hashing.

### VerifyHash

Verifies that an encoded password hash is correct for a given password. It returns `true` if the hash is valid and `false` otherwise.

```csharp
Argon2id.VerifyHash(ReadOnlySpan<char> hash, ReadOnlySpan<byte> password)
```

#### Exceptions

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`hash` has a length less than `MinHashSize` or greater than `MaxHashSize` (internal constants).

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string encoding.

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string termination.

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string prefix.

### NeedsRehash

Determines if an encoded password hash matches the expected iteration count and memory size. It returns `true` if the hash does not match and `false` if the hash matches.

```csharp
Argon2id.NeedsRehash(ReadOnlySpan<char> hash, int iterations, int memorySize)
```

#### Exceptions

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`hash` has a length less than `MinHashSize` or greater than `MaxHashSize` (internal constants).

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`iterations` is less than `MinIterations`.

[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)

`memorySize` is less than `MinMemorySize`.

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string encoding.

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string termination.

[FormatException](https://docs.microsoft.com/en-us/dotnet/api/system.formatexception)

Invalid password hash string prefix.

## Constants

These are used for validation and/or save you defining your own constants.

```csharp
public const int KeySize = 32;
public const int SaltSize = 16;
public const int HashSize = 128;
public const int MinKeySize = 16;
public const int MinIterations = 1;
public const int MinMemorySize = 8192;
```

## Notes

{% hint style="info" %}
The best defence against password cracking will always be to use strong passwords. For example, [diceware](https://www.eff.org/dice) with 6+ words.
{% endhint %}

{% hint style="success" %}
Here are some recommended delays based on scenario:

* Interactive (e.g., online login): 50-250 ms
* Semi-interactive (e.g., file encryption): 250-1000 ms
* Non-interactive (e.g., disk encryption): 1000-5000 ms
  {% endhint %}

Here are some example parameters for different scenarios:

|             Source            | Iterations | Memory (bytes) | \*Delay (ms) |
| :---------------------------: | :--------: | :------------: | :----------: |
|    libsodium's interactive    |      2     |    67108864​   |      51      |
| RFC second recommended option |      3     |    67108864​   |      72      |
|      libsodium's moderate     |      3     |    268435456   |      314     |
|     libsodium's sensitive     |      4     |   1073741824   |     1745     |

**\*These delays are for my desktop**. You should perform benchmarks on a typical device for your application using [BenchmarkArgon2.NET](https://github.com/samuel-lucas6/benchmark-argon2-dotnet).

{% hint style="success" %}
More memory is better than more iterations. However, you will need to increase the iterations in most cases because there should be a limit on how much memory your application uses.
{% endhint %}

{% hint style="warning" %}
Too high of an iteration count/memory size on a server could lead to denial-of-service (DoS) attacks. You can do client-side password hashing as well as server-side password hashing to help, sometimes called [server relief](https://doc.libsodium.org/password_hashing#server-relief).
{% endhint %}

{% hint style="info" %}
The parallelism is always 1 for deriving keys/hashes in libsodium. However, hashes with a parallelism greater than 1 can be verified.
{% endhint %}

{% hint style="info" %}
Libsodium also supports Argon2i, which is more side-channel resistant but less GPU resistant. However, Geralt only supports Argon2id because it is the [mandatory](https://www.rfc-editor.org/rfc/rfc9106.html#name-introduction) and [recommended](https://www.rfc-editor.org/rfc/rfc9106.html#name-recommendations) variant in the RFC, plus there are [attacks](https://en.wikipedia.org/wiki/Argon2#Cryptanalysis) against Argon2i.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.geralt.xyz/password-hashing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
