Argon2

edit

Argon2 (RFC 9106) hashes a password, or derives a key from it. Its cost is set by an amount of memory as well as a number of passes, which makes guessing passwords on dedicated hardware expensive. RFC 9106 recommends the Argon2id variant.

Hash a password

// A random salt of 16 bytes, stored next to the hash
var salt = RandomNumberGenerator.GetBytes(16);

// RFC 9106 section 4: Argon2id with 64 MiB of memory, 3 passes and 4 lanes
var hash = Argon2.DeriveBytes(Argon2Type.Argon2id, "my password"u8, salt, parallelism: 4, memorySizeInKiB: 64 * 1024, passes: 3, outputLength: 32);

// To verify a password, compute the hash with the same parameters and compare in constant time
var other = Argon2.DeriveBytes(Argon2Type.Argon2id, "my password"u8, salt, parallelism: 4, memorySizeInKiB: 64 * 1024, passes: 3, outputLength: 32);
Assert.True(CryptographicOperations.FixedTimeEquals(hash, other));

Argon2.DeriveBytes returns the tag (the hash) as a new array of outputLength bytes. An overload writes it into a Span<byte> instead: the length of the span is the length of the tag.

Unlike BCrypt, the result is only the tag: the package has no string format that also contains the parameters. Store the salt, the variant, the parameters and the tag together, so a password can be verified after you change the parameters for new hashes. To verify a password, compute the tag again with the stored values and compare the two tags with CryptographicOperations.FixedTimeEquals, which takes the same time whatever the position of the first difference.

Parameters

ParameterMeaningAllowed values
typeThe variant: Argon2Type.Argon2id, Argon2i or Argon2dUse Argon2id unless you have a reason not to
passwordThe password, as bytesAny length
saltRandom data, different for each password16 random bytes are recommended
parallelismThe number of lanes1 to Argon2.MaxParallelism
memorySizeInKiBThe memory to use, in kibibytesAt least 8 times parallelism
passesThe number of passes over the memoryAt least 1
outputLengthThe length of the tag, in bytesAt least Argon2.MinOutputLength (4)
secretA secret value (a pepper), kept outside the databaseUsually empty
associatedDataData bound to the tagUsually empty
versionArgon2Version.Version13 (RFC 9106, the default) or Version10Keep the default

A parameter outside its range throws an ArgumentOutOfRangeException.

RFC 9106 (section 4) gives recommended settings, such as 2 GiB of memory with 1 pass, or 64 MiB of memory with 3 passes, both with 4 lanes. Choose the memory size from what your servers can afford for each concurrent sign-in: each call allocates the whole memory, and throws an OutOfMemoryException when it cannot.

Parallelism

The lanes are computed one after the other, on the calling thread: parallelism changes the result, not the number of threads used. Use the value your other systems use, or the one RFC 9106 recommends, and keep it to verify the existing hashes.

Derive a key

The tag can be used as a key, for instance to encrypt data with a password: pass the length of the key as outputLength, and store the salt and the parameters with the encrypted data so the key can be derived again.