Password hashing
Purpose
Argon2id is a memory-hard password hashing and password-based key derivation function (KDF). It takes the following parameters:
A password.
A 128-bit random salt.
An iteration count.
A memory size in bytes.
Set the iteration count to 3.
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.
If the delay is lower than you would like, increase the iterations.
See the Notes for some example parameters.
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:
Only accept ASCII characters in passwords.
If non-ASCII characters are accepted in passwords, apply Unicode Normalization Form C (NFC) to the password before password hashing. This is recommended by NIST SP 800-63B-4 and RFC 8265.
However, note that 2 does not actually fix this problem. There are still rare cases where Unicode characters can be problematic. Normalization also requires having a string copy of the password in memory, which is bad for secure erasure.
Usage
DeriveKey
Fills a span with output keying material computed from a password, a random salt, an iteration count, and a memory size in bytes.
Exceptions
outputKeyingMaterial has a length less than MinKeySize.
salt has a length not equal to SaltSize.
iterations is less than MinIterations.
memorySize is less than MinMemorySize.
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.
hash must be a fixed length due to libsodium's API, which pads the potentially variable-length output with null characters ('\0').
You can convert the hash into a string for storage in a database using Span<T>.ToString(). Any null characters at the end can either be left alone or removed with String.TrimEnd(). Only this hash needs to be stored as the cost parameters and salt are encoded.
Exceptions
hash has a length not equal to HashSize.
iterations is less than MinIterations.
memorySize is less than MinMemorySize.
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.
Exceptions
hash has a length less than MinHashSize or greater than MaxHashSize (internal constants).
Invalid password hash string encoding.
Invalid password hash string termination.
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.
Exceptions
hash has a length less than MinHashSize or greater than MaxHashSize (internal constants).
iterations is less than MinIterations.
memorySize is less than MinMemorySize.
Invalid password hash string encoding.
Invalid password hash string termination.
Invalid password hash string prefix.
Constants
These are used for validation and/or save you defining your own constants.
Notes
The best defence against password cracking will always be to use strong passwords. For example, diceware with 6+ words.
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
Here are some example parameters for different scenarios:
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.
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.
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.
If an attacker can manipulate password hash strings, that also allows DoS because the iteration count/memory size are embedded in the string. Preventing access/tampering or validating against an upper parameter limit can mitigate this.
The parallelism is always 1 for deriving keys/hashes in libsodium. However, hashes with a parallelism greater than 1 can be verified.
Libsodium also supports Argon2i, which is more side-channel resistant but less GPU resistant. However, Geralt only supports Argon2id because it is the mandatory and recommended variant in the RFC, plus there are attacks against Argon2i.
Last updated