Skip to content

Commit

Permalink
src: Check Argon2id encoded hash prefix.
Browse files Browse the repository at this point in the history
MinHashSize is also going back to 93 because Argon2i strings shouldn't be verified using an Argon2id class. Could be smaller (e.g. 72), but libsodium uses this output length. Some other rearranging/code improvements. Hope this is all right as this API isn't great.
  • Loading branch information
samuel-lucas6 committed Oct 1, 2023
1 parent 9694881 commit 41e554d
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 22 deletions.
20 changes: 16 additions & 4 deletions src/Geralt.Tests/Argon2idTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ public static IEnumerable<object[]> StringTestVectors()
public void Constants_Valid()
{
Assert.AreEqual(32, Argon2id.KeySize);
Assert.AreEqual(16, Argon2id.MinKeySize);
Assert.AreEqual(16, Argon2id.SaltSize);
Assert.AreEqual(16, Argon2id.MinKeySize);
Assert.AreEqual(1, Argon2id.MinIterations);
Assert.AreEqual(8192, Argon2id.MinMemorySize);
Assert.AreEqual(92, Argon2id.MinHashSize);
Assert.AreEqual(93, Argon2id.MinHashSize);
Assert.AreEqual(128, Argon2id.MaxHashSize);
Assert.AreEqual("$argon2id$", Argon2id.HashPrefix);
}

[TestMethod]
Expand Down Expand Up @@ -91,7 +90,7 @@ public void DeriveKey_Invalid(int outputKeyingMaterialSize, int passwordSize, in
}

[TestMethod]
[DataRow("correct horse battery staple", 3, 16777216)]
[DataRow("correct horse battery staple", Argon2id.MinIterations, Argon2id.MinMemorySize)]
public void ComputeHash_Valid(string password, int iterations, int memorySize)
{
Span<byte> h = stackalloc byte[Argon2id.MaxHashSize];
Expand Down Expand Up @@ -133,6 +132,17 @@ public void VerifyHash_Valid(bool expected, string hash, string password)
Assert.AreEqual(expected, valid);
}

[TestMethod]
[DataRow("$argon2i$v=19$m=4096,t=3,p=1$eXNtbzQwOTFzajAwMDAwMA$Bb7qAql9aguCTBpLP4PVnlBd+ehJ5rX0R7smB/FggOM", "password")]
[DataRow("$argon2d$v=19$m=4096,t=3,p=1$YTBxd2k1bXBhZHIwMDAwMA$3MM5BChSl8q+MQED0fql0nwP5ykjHdBrGE0mVJHFEUE", "password")]
public void VerifyHash_Tampered(string hash, string password)
{
var h = Encoding.UTF8.GetBytes(hash);
var p = Encoding.UTF8.GetBytes(password);

Assert.ThrowsException<FormatException>(() => Argon2id.VerifyHash(h, p));
}

[TestMethod]
[DataRow(Argon2id.MaxHashSize + 1, Argon2id.KeySize)]
[DataRow(Argon2id.MinHashSize - 1, Argon2id.KeySize)]
Expand Down Expand Up @@ -161,6 +171,8 @@ public void NeedsRehash_Valid(bool expected, string hash, int iterations, int me
[DataRow("argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow("$argon2id$v19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow("$argon2id$v=19$m=16384t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow("$argon2i$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow("$argon2d$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
public void NeedsRehash_Tampered(string hash, int iterations, int memorySize)
{
var h = Encoding.UTF8.GetBytes(hash);
Expand Down
29 changes: 16 additions & 13 deletions src/Geralt/Crypto/Argon2id.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
using static Interop.Libsodium;
using System.Text;
using static Interop.Libsodium;

namespace Geralt;

public static class Argon2id
{
public const int KeySize = 32;
public const int MinKeySize = crypto_pwhash_BYTES_MIN;
public const int SaltSize = crypto_pwhash_SALTBYTES;
public const int MinKeySize = crypto_pwhash_BYTES_MIN;
public const int MinIterations = crypto_pwhash_argon2id_OPSLIMIT_MIN;
public const int MinMemorySize = crypto_pwhash_MEMLIMIT_MIN;
public const int MinHashSize = 92;
public const int MinHashSize = 93;
public const int MaxHashSize = crypto_pwhash_STRBYTES;
public const string HashPrefix = crypto_pwhash_argon2id_STRPREFIX;

private enum Algorithm
{
Argon2id = crypto_pwhash_argon2id_ALG_ARGON2ID13,
Argon2i = crypto_pwhash_argon2i_ALG_ARGON2I13
}
private const string HashPrefix = crypto_pwhash_argon2id_STRPREFIX;

public static unsafe void DeriveKey(Span<byte> outputKeyingMaterial, ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterations, int memorySize)
{
Expand All @@ -28,7 +23,7 @@ public static unsafe void DeriveKey(Span<byte> outputKeyingMaterial, ReadOnlySpa
Sodium.Initialize();
fixed (byte* okm = outputKeyingMaterial, p = password, s = salt)
{
int ret = crypto_pwhash(okm, (ulong)outputKeyingMaterial.Length, p, (ulong)password.Length, s, (ulong)iterations, (nuint)memorySize, (int)Algorithm.Argon2id);
int ret = crypto_pwhash(okm, (ulong)outputKeyingMaterial.Length, p, (ulong)password.Length, s, (ulong)iterations, (nuint)memorySize, crypto_pwhash_argon2id_ALG_ARGON2ID13);
if (ret != 0) { throw new InsufficientMemoryException("Insufficient memory to perform key derivation."); }
}
}
Expand All @@ -41,14 +36,15 @@ public static unsafe void ComputeHash(Span<byte> hash, ReadOnlySpan<byte> passwo
Sodium.Initialize();
fixed (byte* h = hash, p = password)
{
int ret = crypto_pwhash_str_alg(h, p, (ulong)password.Length, (ulong)iterations, (nuint)memorySize, (int)Algorithm.Argon2id);
int ret = crypto_pwhash_str_alg(h, p, (ulong)password.Length, (ulong)iterations, (nuint)memorySize, crypto_pwhash_argon2id_ALG_ARGON2ID13);
if (ret != 0) { throw new InsufficientMemoryException("Insufficient memory to perform password hashing."); }
}
}

public static unsafe bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> password)
{
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
ThrowIfInvalidHashPrefix(hash);
Sodium.Initialize();
fixed (byte* h = hash, p = password)
return crypto_pwhash_str_verify(h, p, (ulong)password.Length) == 0;
Expand All @@ -59,11 +55,18 @@ public static unsafe bool NeedsRehash(ReadOnlySpan<byte> hash, int iterations, i
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
Validation.NotLessThanMin(nameof(iterations), iterations, MinIterations);
Validation.NotLessThanMin(nameof(memorySize), memorySize, MinMemorySize);
ThrowIfInvalidHashPrefix(hash);
Sodium.Initialize();
fixed (byte* h = hash)
{
int ret = crypto_pwhash_str_needs_rehash(h, (ulong)iterations, (nuint)memorySize);
return ret == -1 ? throw new FormatException("Invalid password hash.") : ret == 1;
return ret == -1 ? throw new FormatException("Invalid encoded password hash.") : ret == 1;
}
}

private static void ThrowIfInvalidHashPrefix(ReadOnlySpan<byte> hash)
{
if (!ConstantTime.Equals(hash[..HashPrefix.Length], Encoding.UTF8.GetBytes(HashPrefix)))
throw new FormatException("Invalid encoded password hash prefix.");
}
}
7 changes: 2 additions & 5 deletions src/Geralt/Interop/Interop.Argon2id.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ internal static partial class Interop
{
internal static partial class Libsodium
{
internal const int crypto_pwhash_argon2i_ALG_ARGON2I13 = 1;
internal const int crypto_pwhash_argon2id_ALG_ARGON2ID13 = 2;
internal const int crypto_pwhash_SALTBYTES = 16;
internal const int crypto_pwhash_BYTES_MIN = 16;
internal const int crypto_pwhash_STRBYTES = 128;
internal const string crypto_pwhash_argon2id_STRPREFIX = "$argon2id$";
internal const int crypto_pwhash_MEMLIMIT_MIN = 8192;
internal const long crypto_pwhash_OPSLIMIT_MAX = 4294967295;
internal const int crypto_pwhash_argon2id_OPSLIMIT_MIN = 1;
internal const int crypto_pwhash_argon2i_OPSLIMIT_MIN = 3;
internal const int crypto_pwhash_SALTBYTES = 16;
internal const string crypto_pwhash_argon2id_STRPREFIX = "$argon2id$";

[DllImport(DllName, CallingConvention = Convention)]
internal static extern unsafe int crypto_pwhash(byte* hash, ulong hashLength, byte* password, ulong passwordLength, byte* salt, ulong iterations, nuint memorySize, int algorithm);
Expand Down

0 comments on commit 41e554d

Please sign in to comment.