Skip to content

Commit

Permalink
src: Argon2 string hashes PR cleanup.
Browse files Browse the repository at this point in the history
See #15.
  • Loading branch information
samuel-lucas6 committed Jan 18, 2025
1 parent 2df31dc commit 3821ca1
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 79 deletions.
179 changes: 104 additions & 75 deletions src/Geralt.Tests/Argon2idTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ public void ComputeHash_Valid(string password, int iterations, int memorySize)
Assert.IsFalse(rehash);
}

[TestMethod]
[DataRow("correct horse battery staple", Argon2id.MinIterations, Argon2id.MinMemorySize)]
public void ComputeHash_String_Valid(string password, int iterations, int memorySize)
{
Span<byte> p = Encoding.UTF8.GetBytes(password);

string h = Argon2id.ComputeHash(p, iterations, memorySize);

Assert.IsNotNull(h);

bool valid = Argon2id.VerifyHash(h, p);
Assert.IsTrue(valid);

bool rehash = Argon2id.NeedsRehash(h, iterations, memorySize);
Assert.IsFalse(rehash);
}

[TestMethod]
[DataRow(Argon2id.MaxHashSize + 1, Argon2id.KeySize, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MaxHashSize - 1, Argon2id.KeySize, Argon2id.MinIterations, Argon2id.MinMemorySize)]
Expand All @@ -115,6 +132,16 @@ public void ComputeHash_Invalid(int hashSize, int passwordSize, int iterations,
Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.ComputeHash(h, p, iterations, memorySize));
}

[TestMethod]
[DataRow(Argon2id.KeySize, Argon2id.MinIterations - 1, Argon2id.MinMemorySize)]
[DataRow(Argon2id.KeySize, Argon2id.MinIterations, Argon2id.MinMemorySize - 1)]
public void ComputeHash_String_Invalid(int passwordSize, int iterations, int memorySize)
{
var p = new byte[passwordSize];

Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.ComputeHash(p, iterations, memorySize));
}

[TestMethod]
[DynamicData(nameof(StringTestVectors), DynamicDataSourceType.Method)]
public void VerifyHash_Valid(bool expected, string hash, string password)
Expand All @@ -128,14 +155,14 @@ public void VerifyHash_Valid(bool expected, string hash, string password)
}

[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)
[DynamicData(nameof(StringTestVectors), DynamicDataSourceType.Method)]
public void VerifyHash_String_Valid(bool expected, string hash, string password)
{
var h = Encoding.UTF8.GetBytes(hash);
var p = Encoding.UTF8.GetBytes(password);
Span<byte> p = Encoding.UTF8.GetBytes(password);

Assert.ThrowsException<FormatException>(() => Argon2id.VerifyHash(h, p));
bool valid = Argon2id.VerifyHash(hash, p);

Assert.AreEqual(expected, valid);
}

[TestMethod]
Expand All @@ -150,113 +177,115 @@ public void VerifyHash_Invalid(int hashSize, int passwordSize)
}

[TestMethod]
[DataRow(false, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 4, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 26777216)]
public void NeedsRehash_Valid(bool expected, string hash, int iterations, int memorySize)
[DataRow(null, "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXiho", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcpEltB/ue1kPtBMBGfsysMpPigE6hiNEKZ9v", "")]
public void VerifyHash_String_Invalid(string? hash, string password)
{
Span<byte> h = Encoding.UTF8.GetBytes(hash);

bool rehash = Argon2id.NeedsRehash(h, iterations, memorySize);
var p = Encoding.UTF8.GetBytes(password);

Assert.AreEqual(expected, rehash);
if (hash == null) {
Assert.ThrowsException<ArgumentNullException>(() => Argon2id.VerifyHash(hash!, p));
}
else {
Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.VerifyHash(hash, p));
}
}

[TestMethod]
[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)
[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_WrongPrefix(string hash, string password)
{
var h = Encoding.UTF8.GetBytes(hash);
var p = Encoding.UTF8.GetBytes(password);

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

[TestMethod]
[DataRow(Argon2id.MaxHashSize + 1, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize - 1, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize, Argon2id.MinIterations - 1, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize, Argon2id.MinIterations, Argon2id.MinMemorySize - 1)]
public void NeedsRehash_Invalid(int hashSize, int iterations, int memorySize)
[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_String_WrongPrefix(string hash, string password)
{
var h = new byte[hashSize];
var p = Encoding.UTF8.GetBytes(password);

Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.NeedsRehash(h, iterations, memorySize));
Assert.ThrowsException<FormatException>(() => Argon2id.VerifyHash(hash, p));
}

[TestMethod]
[DataRow("correct horse battery staple", Argon2id.MinIterations, Argon2id.MinMemorySize)]
public void ComputeHash_String_Valid(string password, int iterations, int memorySize)
[DataRow(false, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 4, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 26777216)]
public void NeedsRehash_Valid(bool expected, string hash, int iterations, int memorySize)
{
Span<byte> p = Encoding.UTF8.GetBytes(password);
Span<byte> h = Encoding.UTF8.GetBytes(hash);

string h = Argon2id.ComputeHash(p, iterations, memorySize);
bool rehash = Argon2id.NeedsRehash(h, iterations, memorySize);

Assert.IsNotNull(h);
Assert.AreEqual(expected, rehash);
}

bool valid = Argon2id.VerifyHash(h, p);
Assert.IsTrue(valid);
[TestMethod]
[DataRow(false, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 4, 16777216)]
[DataRow(true, "$argon2id$v=19$m=16384,t=3,p=1$9jzdCOZe8dvfNWga1TS9wQ$ZdlB31msrCUY3R83w6GRGXdmq2zgUcLQGwnedCzU4Us", 3, 26777216)]
public void NeedsRehash_String_Valid(bool expected, string hash, int iterations, int memorySize)
{
bool rehash = Argon2id.NeedsRehash(hash, iterations, memorySize);

bool rehash = Argon2id.NeedsRehash(h, iterations, memorySize);
Assert.IsFalse(rehash);
Assert.AreEqual(expected, rehash);
}

[TestMethod]
[DynamicData(nameof(StringTestVectors), DynamicDataSourceType.Method)]
public void VerifyHash_String_Valid(bool expected, string hash, string password)
[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)
{
Span<byte> p = Encoding.UTF8.GetBytes(password);

bool valid = Argon2id.VerifyHash(hash, p);
var h = Encoding.UTF8.GetBytes(hash);

Assert.AreEqual(expected, valid);
Assert.ThrowsException<FormatException>(() => Argon2id.NeedsRehash(h, iterations, memorySize));
}

[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_String_Tampered(string hash, string password)
[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_String_Tampered(string hash, int iterations, int memorySize)
{
var p = Encoding.UTF8.GetBytes(password);

Assert.ThrowsException<FormatException>(() => Argon2id.VerifyHash(hash, p));
Assert.ThrowsException<FormatException>(() => Argon2id.NeedsRehash(hash, iterations, memorySize));
}

[TestMethod]
[DataRow("$argon2id$", "")]
[DataRow("$argon2id$v=1", "")]
[DataRow("$argon2id$v=19", "")]
[DataRow("$argon2id$v=19$", "")]
[DataRow("$argon2id$v=19$m=4882,t=", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiX", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcp", "")]
public void VerifyHash_String_Invalid(string hash, string password)
[DataRow(Argon2id.MaxHashSize + 1, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize - 1, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize, Argon2id.MinIterations - 1, Argon2id.MinMemorySize)]
[DataRow(Argon2id.MinHashSize, Argon2id.MinIterations, Argon2id.MinMemorySize - 1)]
public void NeedsRehash_Invalid(int hashSize, int iterations, int memorySize)
{
var p = Encoding.UTF8.GetBytes(password);
var h = new byte[hashSize];

bool valid = Argon2id.VerifyHash(hash, p);
Assert.IsFalse(valid);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.NeedsRehash(h, iterations, memorySize));
}

[TestMethod]
[DataRow("$argon2id$", "")]
[DataRow("$argon2id$v=1", "")]
[DataRow("$argon2id$v=19", "")]
[DataRow("$argon2id$v=19$", "")]
[DataRow("$argon2id$v=19$m=4882,t=", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiX", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7", "")]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcp", "")]
public void NeedsRehash_String_Invalid(string hash, string password)
[DataRow(null, Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXiho", Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcpEltB/ue1kPtBMBGfsysMpPigE6hiNEKZ9v", Argon2id.MinIterations, Argon2id.MinMemorySize)]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcpE", Argon2id.MinIterations - 1, Argon2id.MinMemorySize)]
[DataRow("$argon2id$v=19$m=4882,t=2,p=1$bA81arsiXysd3WbTRzmEOw$Nm8QBM+7RH1DXo9rvp5cwKEOOOfD2g6JuxlXihoNcpE", Argon2id.MinIterations, Argon2id.MinMemorySize - 1)]
public void NeedsRehash_String_Invalid(string? hash, int iterations, int memorySize)
{
var p = Encoding.UTF8.GetBytes(password);

Assert.ThrowsException<FormatException>(() => Argon2id.NeedsRehash(hash, Argon2id.MinIterations, Argon2id.MinMemorySize));
if (hash == null) {
Assert.ThrowsException<ArgumentNullException>(() => Argon2id.NeedsRehash(hash!, iterations, memorySize));
}
else {
Assert.ThrowsException<ArgumentOutOfRangeException>(() => Argon2id.NeedsRehash(hash, iterations, memorySize));
}
}
}
10 changes: 6 additions & 4 deletions src/Geralt/Crypto/Argon2id.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static class Argon2id
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 = 93;
public const int MinHashSize = 93; // Smallest possible Argon2id string that libsodium can generate
public const int MaxHashSize = crypto_pwhash_STRBYTES;
private const string HashPrefix = crypto_pwhash_argon2id_STRPREFIX;

Expand Down Expand Up @@ -47,9 +47,9 @@ public static string ComputeHash(ReadOnlySpan<byte> password, int iterations, in
if (ret != 0) { throw new InsufficientMemoryException("Insufficient memory to perform password hashing."); }
return Marshal.PtrToStringAnsi(hash)!;
}
finally {
Marshal.FreeHGlobal(hash);
}
finally {
Marshal.FreeHGlobal(hash);
}
}

public static bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> password)
Expand All @@ -63,6 +63,7 @@ public static bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> passwo
public static bool VerifyHash(string hash, ReadOnlySpan<byte> password)
{
Validation.NotNull(nameof(hash), hash);
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
ThrowIfInvalidHashPrefix(hash);
Sodium.Initialize();
return crypto_pwhash_str_verify(hash, password, (ulong)password.Length) == 0;
Expand All @@ -82,6 +83,7 @@ public static bool NeedsRehash(ReadOnlySpan<byte> hash, int iterations, int memo
public static bool NeedsRehash(string hash, int iterations, int memorySize)
{
Validation.NotNull(nameof(hash), hash);
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
Validation.NotLessThanMin(nameof(iterations), iterations, MinIterations);
Validation.NotLessThanMin(nameof(memorySize), memorySize, MinMemorySize);
ThrowIfInvalidHashPrefix(hash);
Expand Down

0 comments on commit 3821ca1

Please sign in to comment.