aboutsummaryrefslogtreecommitdiff
path: root/lib/Hashing.Portable
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-29 21:23:26 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-29 21:23:26 -0500
commitb679ddd4e647ac915febd0d5a5e488a1e8e48842 (patch)
treecf414be9a53342e8d59194198cde5bf3c2187fc1 /lib/Hashing.Portable
parent2b1314c1475e7e1831c691cf349cb89c66fa320c (diff)
Squashed commit of the following:
commit 231e26e5c6731e6e156d7c0591518e84a3b82f5a Author: vnugent <public@vaughnnugent.com> Date: Thu Feb 29 20:59:42 2024 -0500 fix: #5 find and patch Windows compression bug and some deployment commit d0bfe14e0a0e27172b8dd41f468265e651784837 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 21 21:39:19 2024 -0500 fix: #4 fix readme licensing message for accuracy commit 6f37f152fcd105e40af6689192a36b87eda95f51 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 21 21:37:55 2024 -0500 fix: jwt hashalg sizing and public api for hashalg sizes
Diffstat (limited to 'lib/Hashing.Portable')
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs7
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs10
-rw-r--r--lib/Hashing.Portable/src/ManagedHash.cs57
-rw-r--r--lib/Hashing.Portable/src/ManagedHashAlgImpl.cs29
-rw-r--r--lib/Hashing.Portable/tests/ManagedHashTests.cs81
5 files changed, 141 insertions, 43 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
index 2a18cfc..12ba095 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
@@ -194,6 +194,13 @@ namespace VNLib.Hashing.IdentityUtility
}
/// <summary>
+ /// Gets the hash size in bytes for the current <see cref="HashAlg"/> value
+ /// </summary>
+ /// <param name="alg"></param>
+ /// <returns>The size (in bytes) of the algorithm's hash</returns>
+ public static int HashSize(this HashAlg alg) => ManagedHash.GetHashSize(alg);
+
+ /// <summary>
/// Gets the <see cref="HashAlgorithmName"/> for the current <see cref="HashAlg"/>
/// value.
/// </summary>
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
index ef6715b..68033cd 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
@@ -275,7 +275,7 @@ namespace VNLib.Hashing.IdentityUtility
public static void Sign(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg)
{
//Stack hash output buffer, will be the size of the alg
- Span<byte> sigOut = stackalloc byte[(int)alg];
+ Span<byte> sigOut = stackalloc byte[alg.HashSize()];
//Compute
ERRNO count = ManagedHash.ComputeHmac(key, jwt.HeaderAndPayload, sigOut, alg);
@@ -394,7 +394,7 @@ namespace VNLib.Hashing.IdentityUtility
int sigBufSize = CalcPadding(signature.Length) + signature.Length;
//Calc full buffer size
- nint bufferSize = MemoryUtil.NearestPage(sigBufSize + (int)alg);
+ nint bufferSize = MemoryUtil.NearestPage(sigBufSize + alg.HashSize());
//Alloc buffer to decode data, as a full page, all buffers will be used from the block for better cache
using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>((int)bufferSize);
@@ -442,10 +442,10 @@ namespace VNLib.Hashing.IdentityUtility
/// <exception cref="InternalBufferTooSmallException"></exception>
public static bool Verify(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg)
{
- _ = jwt ?? throw new ArgumentNullException(nameof(jwt));
+ ArgumentNullException.ThrowIfNull(jwt);
//Get base64 buffer size for in-place conversion
- int bufferSize = Base64.GetMaxEncodedToUtf8Length((int)alg);
+ int bufferSize = Base64.GetMaxEncodedToUtf8Length(alg.HashSize());
//Alloc buffer for signature output
Span<byte> signatureBuffer = stackalloc byte[bufferSize];
@@ -458,7 +458,7 @@ namespace VNLib.Hashing.IdentityUtility
}
//Do an in-place base64 conversion of the signature to base64url
- ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, (int)alg, false);
+ ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, alg.HashSize(), false);
if (!encoded)
{
diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs
index 7791eed..429660f 100644
--- a/lib/Hashing.Portable/src/ManagedHash.cs
+++ b/lib/Hashing.Portable/src/ManagedHash.cs
@@ -29,7 +29,6 @@ using System.Security.Cryptography;
using VNLib.Utils;
using VNLib.Utils.Memory;
-using VNLib.Hashing.Native.MonoCypher;
namespace VNLib.Hashing
{
@@ -84,7 +83,7 @@ namespace VNLib.Hashing
/// Gets a value that indicates whether the current runtime has the required libraries
/// available to support the Blake2b hashing algorithm
/// </summary>
- public static bool SupportsBlake2b => MonoCypherLibrary.CanLoadDefaultLibrary();
+ public static bool SupportsBlake2b => IsAlgSupported(HashAlg.BlAKE2B);
/// <summary>
/// Gets a value that indicates whether the current platform supports the SHA3
@@ -103,8 +102,8 @@ namespace VNLib.Hashing
HashAlg.SHA3_512 => Sha3_512.IsSupported,
HashAlg.SHA3_384 => Sha3_384.IsSupported,
HashAlg.SHA3_256 => Sha3_256.IsSupported,
- HashAlg.BlAKE2B => SupportsBlake2b,
- HashAlg.SHA512 => true,
+ HashAlg.BlAKE2B => Blake2b.IsSupported,
+ HashAlg.SHA512 => true, //Built-in functions are always supported
HashAlg.SHA384 => true,
HashAlg.SHA256 => true,
HashAlg.SHA1 => true,
@@ -113,6 +112,26 @@ namespace VNLib.Hashing
};
/// <summary>
+ /// Gets the size of the hash (in bytes) for the specified algorithm
+ /// </summary>
+ /// <param name="type">The hash algorithm to get the size of</param>
+ /// <returns>A positive 32-bit integer size (in bytes) of the algorithm hash size</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static int GetHashSize(HashAlg type) => type switch
+ {
+ HashAlg.SHA3_512 => _3_sha512.HashSize,
+ HashAlg.SHA3_384 => _3_sha384.HashSize,
+ HashAlg.SHA3_256 => _3_sha256.HashSize,
+ HashAlg.BlAKE2B => _blake2bAlg.HashSize,
+ HashAlg.SHA512 => _sha512Alg.HashSize,
+ HashAlg.SHA384 => _sha384Alg.HashSize,
+ HashAlg.SHA256 => _sha256Alg.HashSize,
+ HashAlg.SHA1 => _sha1Alg.HashSize,
+ HashAlg.MD5 => _md5Alg.HashSize,
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
+ };
+
+ /// <summary>
/// Uses the UTF8 character encoding to encode the string, then
/// attempts to compute the hash and store the results into the output buffer
/// </summary>
@@ -265,11 +284,11 @@ namespace VNLib.Hashing
private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, ReadOnlySpan<byte> key = default)
{
//Alloc output buffer
- byte[] output = new byte[HashSize(alg)];
+ byte[] output = new byte[GetHashSize(alg)];
//Hash data
ERRNO result = ComputeHashInternal(alg, data, output, key);
- Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
+ Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
return output;
}
@@ -288,13 +307,13 @@ namespace VNLib.Hashing
private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default)
{
//Alloc stack buffer to store hash output
- Span<byte> hashBuffer = stackalloc byte[HashSize(alg)];
+ Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)];
//hash the buffer
ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key);
//Count should always be the same as the hash size, this should never fail
- Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
+ Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
//Convert to encoded string
return mode switch
@@ -310,13 +329,13 @@ namespace VNLib.Hashing
private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default)
{
//Alloc stack buffer to store hash output
- Span<byte> hashBuffer = stackalloc byte[HashSize(alg)];
+ Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)];
//hash the buffer
ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key);
//Count should always be the same as the hash size, this should never fail
- Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
+ Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
//Convert to encoded string
return mode switch
@@ -333,29 +352,15 @@ namespace VNLib.Hashing
private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key = default)
{
//Alloc output buffer
- byte[] output = new byte[HashSize(alg)];
+ byte[] output = new byte[GetHashSize(alg)];
//Hash data
ERRNO result = ComputeHashInternal(alg, data, output, key);
- Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
+ Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
return output;
}
- private static int HashSize(HashAlg alg) => alg switch
- {
- HashAlg.SHA3_512 => _3_sha512.HashSize,
- HashAlg.SHA3_384 => _3_sha384.HashSize,
- HashAlg.SHA3_256 => _3_sha256.HashSize,
- HashAlg.BlAKE2B => _blake2bAlg.HashSize,
- HashAlg.SHA512 => _sha512Alg.HashSize,
- HashAlg.SHA384 => _sha384Alg.HashSize,
- HashAlg.SHA256 => _sha256Alg.HashSize,
- HashAlg.SHA1 => _sha1Alg.HashSize,
- HashAlg.MD5 => _md5Alg.HashSize,
- _ => throw new ArgumentException("Invalid hash algorithm", nameof(alg))
- };
-
private static ERRNO ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, Span<byte> buffer, ReadOnlySpan<byte> key = default)
{
diff --git a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
index d53152e..eae8903 100644
--- a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
+++ b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
@@ -150,6 +150,8 @@ namespace VNLib.Hashing
internal static int MaxHashSize => MCBlake2Module.MaxHashSize;
internal static int MaxKeySize => MCBlake2Module.MaxKeySize;
+ public static bool IsSupported => MonoCypherLibrary.CanLoadDefaultLibrary();
+
///<inheritdoc/>
public readonly int HashSize => DefaultBlake2HashSize;
@@ -180,9 +182,18 @@ namespace VNLib.Hashing
{
count = 0;
- if (output.Length > MCBlake2Module.MaxHashSize)
+ /*
+ * For this specific implementation, the ManagedHash class assumes hashing implementations
+ * have a fixed hash size. This is not the case for Blake2b, so we need to enforce one.
+ *
+ * Since the HasSize property is publicly accessible users can assume the hash size is
+ * the value returned by the property. So it should be safe to enforce the hash size here.
+ *
+ * Any data outside the default hash size is not our problem.
+ */
+ if (output.Length > HashSize)
{
- return false;
+ output = output[..HashSize];
}
//Test key size
@@ -191,17 +202,11 @@ namespace VNLib.Hashing
return false;
}
- //Compute one-shot hash
- ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output);
+ //Compute one-shot hash, a variable length result may occur
+ count = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output);
- if (result < output.Length)
- {
- count = 0;
- return false;
- }
-
- count = output.Length;
- return true;
+ //Output should always be the same size as the hash size
+ return count == output.Length;
}
}
}
diff --git a/lib/Hashing.Portable/tests/ManagedHashTests.cs b/lib/Hashing.Portable/tests/ManagedHashTests.cs
index a9fa467..3bcdb33 100644
--- a/lib/Hashing.Portable/tests/ManagedHashTests.cs
+++ b/lib/Hashing.Portable/tests/ManagedHashTests.cs
@@ -97,5 +97,86 @@ namespace VNLib.Hashing.Tests
Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2);
}
}
+
+ const string TestHashInput = "Hello World!";
+ static readonly string[] TestHashOutputHex =
+ [
+ //None
+ "",
+ //Md5
+ "ed076287532e86365e841e92bfc50d8c",
+ //Sha1
+ "2ef7bde608ce5404e97d5f042f95f89f1c232871",
+ //Sha2
+ "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069",
+ "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a",
+ "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8",
+
+ //Sha3
+ "d0e47486bbf4c16acac26f8b653592973c1362909f90262877089f9c8a4536af",
+ "f324cbd421326a2abaedf6f395d1a51e189d4a71c755f531289e519f079b224664961e385afcc37da348bd859f34fd1c",
+ "32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48",
+
+ //Blake2b (64 bytes/512 bits)
+ "54b113f499799d2f3c0711da174e3bc724737ad18f63feb286184f0597e1466436705d6c8e8c7d3d3b88f5a22e83496e0043c44a3c2b1700e0e02259f8ac468e"
+ ];
+
+ //Known hash sizes to compare against
+ static readonly int[] HashSizes =
+ [
+ 0,
+ 16,
+ 20,
+ 32,
+ 48,
+ 64,
+ 32,
+ 48,
+ 64,
+ 64
+ ];
+
+ [TestMethod()]
+ public void ComputeHexHashTest()
+ {
+ HashAlg[] algs = Enum.GetValues<HashAlg>();
+
+ Assert.AreEqual(algs.Length, TestHashOutputHex.Length);
+
+ for (int i = 0; i < algs.Length; i++)
+ {
+ if (algs[i] == HashAlg.None)
+ continue;
+
+ //Skip unsupported algorithms
+ if (!ManagedHash.IsAlgSupported(algs[i]))
+ continue;
+
+ string hash = ManagedHash.ComputeHash(TestHashInput, algs[i], HashEncodingMode.Hexadecimal);
+
+ //Make sure exact length (x2 for hex)
+ Assert.AreEqual(HashSizes[i] * 2, hash.Length);
+
+ //Make sure exact value
+ Assert.AreEqual(TestHashOutputHex[i], hash, true);
+ }
+ }
+
+ [TestMethod()]
+ public void AlgSizeTest()
+ {
+ HashAlg[] algs = Enum.GetValues<HashAlg>();
+
+ for (int i = 0; i < algs.Length; i++)
+ {
+ if (algs[i] == HashAlg.None)
+ continue;
+
+ //Make sure exact length
+ Assert.AreEqual(HashSizes[i], ManagedHash.GetHashSize(algs[i]));
+ }
+
+ Assert.ThrowsException<ArgumentException>(() => ManagedHash.GetHashSize(HashAlg.None));
+ }
}
} \ No newline at end of file