Skip to content

Commit

Permalink
Argon2id.cs: Provide overloads that accept/return strings
Browse files Browse the repository at this point in the history
  • Loading branch information
ektrah committed Dec 11, 2024
1 parent a51a979 commit 174a475
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
55 changes: 55 additions & 0 deletions src/Geralt.Tests/Argon2idTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,59 @@ public void NeedsRehash_Invalid(int hashSize, int iterations, int memorySize)

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

[TestMethod]
[DataRow("correct horse battery staple", Argon2id.MinIterations, Argon2id.MinMemorySize)]
public void ComputeHash_Valid_String(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]
[DynamicData(nameof(StringTestVectors), DynamicDataSourceType.Method)]
public void VerifyHash_Valid_String(bool expected, string hash, string password)
{
Span<byte> p = Encoding.UTF8.GetBytes(password);

bool valid = Argon2id.VerifyHash(hash, p);

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

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

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

Assert.IsFalse(Argon2id.VerifyHash(hash, p));
}
}
41 changes: 41 additions & 0 deletions src/Geralt/Crypto/Argon2id.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ public static void ComputeHash(Span<byte> hash, ReadOnlySpan<byte> password, int
if (ret != 0) { throw new InsufficientMemoryException("Insufficient memory to perform password hashing."); }
}

public static string ComputeHash(ReadOnlySpan<byte> password, int iterations, int memorySize)
{
Validation.NotLessThanMin(nameof(iterations), iterations, MinIterations);
Validation.NotLessThanMin(nameof(memorySize), memorySize, MinMemorySize);
Sodium.Initialize();
unsafe
{
sbyte* hash = stackalloc sbyte[crypto_pwhash_STRBYTES];
int ret = crypto_pwhash_str_alg(hash, password, (ulong)password.Length, (ulong)iterations, (nuint)memorySize, crypto_pwhash_argon2id_ALG_ARGON2ID13);
if (ret != 0) { throw new InsufficientMemoryException("Insufficient memory to perform password hashing."); }
return new string(hash);
}
}

public static bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> password)
{
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
Expand All @@ -43,6 +57,14 @@ public static bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> passwo
return crypto_pwhash_str_verify(hash, password, (ulong)password.Length) == 0;
}

public static bool VerifyHash(string hash, ReadOnlySpan<byte> password)
{
Validation.NotNull(nameof(hash), hash);
ThrowIfInvalidHashPrefix(hash);
Sodium.Initialize();
return crypto_pwhash_str_verify(hash, password, (ulong)password.Length) == 0;
}

public static bool NeedsRehash(ReadOnlySpan<byte> hash, int iterations, int memorySize)
{
Validation.SizeBetween(nameof(hash), hash.Length, MinHashSize, MaxHashSize);
Expand All @@ -54,10 +76,29 @@ public static bool NeedsRehash(ReadOnlySpan<byte> hash, int iterations, int memo
return ret == -1 ? throw new FormatException("Invalid encoded password hash.") : ret == 1;
}

public static bool NeedsRehash(string hash, int iterations, int memorySize)
{
Validation.NotNull(nameof(hash), hash);
Validation.NotLessThanMin(nameof(iterations), iterations, MinIterations);
Validation.NotLessThanMin(nameof(memorySize), memorySize, MinMemorySize);
ThrowIfInvalidHashPrefix(hash);
Sodium.Initialize();
int ret = crypto_pwhash_str_needs_rehash(hash, (ulong)iterations, (nuint)memorySize);
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.");
}
}

private static void ThrowIfInvalidHashPrefix(string hash)
{
if (!hash.StartsWith(HashPrefix))
{
throw new FormatException("Invalid encoded password hash prefix.");
}
}
}
12 changes: 12 additions & 0 deletions src/Geralt/Interop/Interop.Argon2id.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ internal static partial class Libsodium
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_pwhash_str_alg(Span<byte> hash, ReadOnlySpan<byte> password, ulong passwordLength, ulong iterations, nuint memorySize, int algorithm);

[LibraryImport(DllName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static unsafe partial int crypto_pwhash_str_alg(sbyte* hash, ReadOnlySpan<byte> password, ulong passwordLength, ulong iterations, nuint memorySize, int algorithm);

[LibraryImport(DllName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_pwhash_str_verify(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> password, ulong passwordLength);

[LibraryImport(DllName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_pwhash_str_verify([MarshalAs(UnmanagedType.LPStr)] string hash, ReadOnlySpan<byte> password, ulong passwordLength);

[LibraryImport(DllName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_pwhash_str_needs_rehash(ReadOnlySpan<byte> hash, ulong iterations, nuint memorySize);

[LibraryImport(DllName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_pwhash_str_needs_rehash([MarshalAs(UnmanagedType.LPStr)] string hash, ulong iterations, nuint memorySize);
}
}

0 comments on commit 174a475

Please sign in to comment.