aboutsummaryrefslogtreecommitdiff
path: root/lib/Hashing.Portable/src
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-12-20 18:33:32 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-12-20 18:33:32 -0500
commit43542a261ec0789c7e48551ea5f9eaefa8c4b772 (patch)
tree35d136732bf2a44780a156da6f9963d1ebb2201d /lib/Hashing.Portable/src
parent546abea662263ef112c571c29706c47e875e09c4 (diff)
monocypher vendor and wrapper started, and partial public api updates
Diffstat (limited to 'lib/Hashing.Portable/src')
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2Context.cs44
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs58
-rw-r--r--lib/Hashing.Portable/src/HashAlg.cs72
-rw-r--r--lib/Hashing.Portable/src/HashEncodingMode.cs52
-rw-r--r--lib/Hashing.Portable/src/ManagedHash.cs369
-rw-r--r--lib/Hashing.Portable/src/ManagedHashAlgImpl.cs227
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs54
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs46
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs420
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs292
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs159
-rw-r--r--lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs169
12 files changed, 1741 insertions, 221 deletions
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2Context.cs b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs
index 4631654..37abfbd 100644
--- a/lib/Hashing.Portable/src/Argon2/Argon2Context.cs
+++ b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs
@@ -27,38 +27,34 @@ using System.Runtime.InteropServices;
namespace VNLib.Hashing
{
-
- public static unsafe partial class VnArgon2
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ internal unsafe ref struct Argon2Context
{
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- private ref struct Argon2Context
- {
- public void* outptr; /* output array */
- public UInt32 outlen; /* digest length */
+ public void* outptr; /* output array */
+ public UInt32 outlen; /* digest length */
- public void* pwd; /* password array */
- public UInt32 pwdlen; /* password length */
+ public void* pwd; /* password array */
+ public UInt32 pwdlen; /* password length */
- public void* salt; /* salt array */
- public UInt32 saltlen; /* salt length */
+ public void* salt; /* salt array */
+ public UInt32 saltlen; /* salt length */
- public void* secret; /* key array */
- public UInt32 secretlen; /* key length */
+ public void* secret; /* key array */
+ public UInt32 secretlen; /* key length */
- public void* ad; /* associated data array */
- public UInt32 adlen; /* associated data length */
+ public void* ad; /* associated data array */
+ public UInt32 adlen; /* associated data length */
- public UInt32 t_cost; /* number of passes */
- public UInt32 m_cost; /* amount of memory requested (KB) */
- public UInt32 lanes; /* number of lanes */
- public UInt32 threads; /* maximum number of threads */
+ public UInt32 t_cost; /* number of passes */
+ public UInt32 m_cost; /* amount of memory requested (KB) */
+ public UInt32 lanes; /* number of lanes */
+ public UInt32 threads; /* maximum number of threads */
- public Argon2Version version; /* version number */
+ public Argon2Version version; /* version number */
- public void* allocate_cbk; /* pointer to memory allocator */
- public void* free_cbk; /* pointer to memory deallocator */
+ public void* allocate_cbk; /* pointer to memory allocator */
+ public void* free_cbk; /* pointer to memory deallocator */
- public UInt32 flags; /* array of bool options */
- }
+ public UInt32 flags; /* array of bool options */
}
} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
index 9b27194..9d98050 100644
--- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
+++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
@@ -24,7 +24,6 @@
using System;
using System.Text;
-using System.Buffers;
using System.Threading;
using System.Diagnostics;
using System.Buffers.Text;
@@ -34,6 +33,7 @@ using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Native;
using VNLib.Utils.Extensions;
+using VNLib.Hashing.Native.MonoCypher;
namespace VNLib.Hashing
{
@@ -42,40 +42,46 @@ namespace VNLib.Hashing
/// Implements the Argon2 data hashing library in .NET for cross platform use.
/// </summary>
/// <remarks>Buffers are allocted on a private <see cref="IUnmangedHeap"/> instance.</remarks>
- public static unsafe partial class VnArgon2
+ public static unsafe class VnArgon2
{
public const uint ARGON2_DEFAULT_FLAGS = 0U;
public const uint HASH_SIZE = 128;
public const int MAX_SALT_SIZE = 100;
public const string ID_MODE = "argon2id";
public const string ARGON2_DEFUALT_LIB_NAME = "argon2";
- public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH";
+ public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_ARGON2_DLL_PATH";
private static readonly Encoding LocEncoding = Encoding.Unicode;
private static readonly Lazy<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true), LazyThreadSafetyMode.PublicationOnly);
- private static readonly Lazy<SafeArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly);
+ private static readonly Lazy<IArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly);
//Private heap initialized to 10k size, and all allocated buffers will be zeroed when allocated
- private static IUnmangedHeap PwHeap => _heap.Value;
+ private static IUnmangedHeap PwHeap => _heap.Value;
- /*
- * The native library delegate method
- */
- [SafeMethodName("argon2id_ctx")]
- delegate int Argon2InvokeHash(IntPtr context);
-
- private static SafeArgon2Library LoadSharedLibInternal()
+ private static IArgon2Library LoadSharedLibInternal()
{
//Get the path to the argon2 library
string? argon2EnvPath = Environment.GetEnvironmentVariable(ARGON2_LIB_ENVIRONMENT_VAR_NAME);
- //Default to the default library name
- argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME;
- Trace.WriteLine("Attempting to load global native Argon2 library from: " + argon2EnvPath, "VnArgon2");
+ //If no native library is set, try to load the monocypher library
+ if (string.IsNullOrWhiteSpace(argon2EnvPath) && MonoCypherLibrary.CanLoadDefaultLibrary())
+ {
+ Trace.WriteLine("Using the native MonoCypher library for Argon2 password hashing", "VnArgon2");
- SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories);
- return new SafeArgon2Library(lib);
+ //Load shared monocyphter argon2 library
+ return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Value);
+ }
+ else
+ {
+ //Default to the default library name
+ argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME;
+
+ Trace.WriteLine("Attempting to load global native Argon2 library from: " + argon2EnvPath, "VnArgon2");
+
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories);
+ return new SafeArgon2Library(lib);
+ }
}
@@ -130,7 +136,7 @@ namespace VNLib.Hashing
int passBytes = LocEncoding.GetByteCount(password);
//Alloc memory for salt
- using IMemoryHandle<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes);
+ using MemoryHandle<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes);
Span<byte> saltBuffer = buffer.AsSpan(0, saltbytes);
Span<byte> passBuffer = buffer.AsSpan(saltbytes, passBytes);
@@ -175,7 +181,7 @@ namespace VNLib.Hashing
int passBytes = LocEncoding.GetByteCount(password);
//Alloc memory for password
- using IMemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes);
+ using MemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes);
//Encode password, create a new span to make sure its proper size
_ = LocEncoding.GetBytes(password, pwdHandle.Span);
@@ -223,9 +229,6 @@ namespace VNLib.Hashing
//encode salt
salts = Convert.ToBase64String(salt);
- //Zero buffer
- MemoryUtil.InitializeBlock(ref hashHandle.GetReference(), hashHandle.GetIntLength());
-
//Encode salt in base64
return $"${ID_MODE}$v={(int)Argon2Version.Version13},m={costParams.MemoryCost},t={costParams.TimeCost},p={costParams.Parallelism},s={salts}${hash}";
}
@@ -330,7 +333,7 @@ namespace VNLib.Hashing
int rawPassLen = LocEncoding.GetByteCount(rawPass);
//Alloc buffer for decoded data
- using IMemoryHandle<byte> rawBufferHandle = PwHeap.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen);
+ using MemoryHandle<byte> rawBufferHandle = PwHeap.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen);
//Split buffers
Span<byte> saltBuf = rawBufferHandle.Span[..saltBase64BufSize];
@@ -393,10 +396,7 @@ namespace VNLib.Hashing
)
{
//Alloc data for hash output
- using IMemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length);
-
- //Pin to get the base pointer
- using MemoryHandle outputPtr = outputHandle.Pin(0);
+ using MemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length);
//Get pointers
fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt)
@@ -425,7 +425,7 @@ namespace VNLib.Hashing
context->secret = secretptr;
context->secretlen = (uint)secret.Length;
//Output
- context->outptr = outputPtr.Pointer;
+ context->outptr = outputHandle.Base;
context->outlen = (uint)outputHandle.Length;
//Hash
Argon2_ErrorCodes argResult = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context);
@@ -441,7 +441,7 @@ namespace VNLib.Hashing
return result;
}
- private static void ThrowOnArgonErr(Argon2_ErrorCodes result)
+ internal static void ThrowOnArgonErr(Argon2_ErrorCodes result)
{
switch (result)
{
diff --git a/lib/Hashing.Portable/src/HashAlg.cs b/lib/Hashing.Portable/src/HashAlg.cs
new file mode 100644
index 0000000..030e7e6
--- /dev/null
+++ b/lib/Hashing.Portable/src/HashAlg.cs
@@ -0,0 +1,72 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: HashAlg.cs
+*
+* HashAlg.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using VNLib.Hashing.Native.MonoCypher;
+
+namespace VNLib.Hashing
+{
+ /// <summary>
+ /// Defines a hashing algorithm to use when computing a hash.
+ /// </summary>
+ public enum HashAlg
+ {
+ /// <summary>
+ /// Unused type, will cause a computation method to raise an argument exception when used.
+ /// </summary>
+ None,
+ /// <summary>
+ /// Defines the SHA-512 hashing algorithm
+ /// </summary>
+ SHA512 = 64,
+ /// <summary>
+ /// Defines the SHA-384 hashing algorithm
+ /// </summary>
+ SHA384 = 48,
+ /// <summary>
+ /// Defines the SHA-256 hashing algorithm
+ /// </summary>
+ SHA256 = 32,
+ /// <summary>
+ /// Defines the SHA-1 hashing algorithm
+ /// WARNING: This hashing method is considered insecure and cannot be corrected.
+ /// </summary>
+ SHA1 = 20,
+ /// <summary>
+ /// Defines the MD5 hashing algorithm
+ /// WARNING: This hashing method is considered insecure and cannot be corrected.
+ /// </summary>
+ MD5 = 16,
+
+ /*
+ * The blake2 value is negative because the hash size is variable and the enum value
+ * and cannot be used to determine the hash buffer size.
+ */
+ /// <summary>
+ /// Defines the BLAKE2B hashing algorithm
+ /// NOTE: This hashing method may not be supported on all platforms, you should check for support before using it.
+ /// Inspect the value of <see cref="ManagedHash.SupportsBlake2b"/>
+ /// </summary>
+ BlAKE2B = -MCBlake2Module.MaxHashSize,
+ }
+}
diff --git a/lib/Hashing.Portable/src/HashEncodingMode.cs b/lib/Hashing.Portable/src/HashEncodingMode.cs
new file mode 100644
index 0000000..cc1aa8c
--- /dev/null
+++ b/lib/Hashing.Portable/src/HashEncodingMode.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: HashEncodingMode.cs
+*
+* HashEncodingMode.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Hashing
+{
+ /// <summary>
+ /// The binary hash encoding type
+ /// </summary>
+ [Flags]
+ public enum HashEncodingMode
+ {
+ /// <summary>
+ /// Specifies the Base64 character encoding
+ /// </summary>
+ Base64 = 64,
+ /// <summary>
+ /// Specifies the hexadecimal character encoding
+ /// </summary>
+ Hexadecimal = 16,
+ /// <summary>
+ /// Specifies the Base32 character encoding
+ /// </summary>
+ Base32 = 32,
+ /// <summary>
+ /// Specifies the base64 URL safe character encoding
+ /// </summary>
+ Base64Url = 128
+ }
+}
diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs
index 43c2a7c..5933804 100644
--- a/lib/Hashing.Portable/src/ManagedHash.cs
+++ b/lib/Hashing.Portable/src/ManagedHash.cs
@@ -28,61 +28,11 @@ using System.Security.Cryptography;
using VNLib.Utils;
using VNLib.Utils.Memory;
+using VNLib.Hashing.Native.MonoCypher;
+using VNLib.Utils.Extensions;
namespace VNLib.Hashing
{
- /// <summary>
- /// Defines a hashing algorithm to use when computing a hash.
- /// </summary>
- public enum HashAlg
- {
- /// <summary>
- /// Unused type, will cause a computation method to raise an argument exception when used.
- /// </summary>
- None,
- /// <summary>
- /// Defines the SHA-512 hashing algorithm
- /// </summary>
- SHA512 = 64,
- /// <summary>
- /// Defines the SHA-384 hashing algorithm
- /// </summary>
- SHA384 = 48,
- /// <summary>
- /// Defines the SHA-256 hashing algorithm
- /// </summary>
- SHA256 = 32,
- /// <summary>
- /// Defines the SHA-1 hashing algorithm
- /// WARNING: This hashing method is considered insecure and cannot be corrected.
- /// </summary>
- SHA1 = 20,
- /// <summary>
- /// Defines the MD5 hashing algorithm
- /// WARNING: This hashing method is considered insecure and cannot be corrected.
- /// </summary>
- MD5 = 16
- }
-
- /// <summary>
- /// The binary hash encoding type
- /// </summary>
- [Flags]
- public enum HashEncodingMode
- {
- /// <summary>
- /// Specifies the Base64 character encoding
- /// </summary>
- Base64 = 64,
- /// <summary>
- /// Specifies the hexadecimal character encoding
- /// </summary>
- Hexadecimal = 16,
- /// <summary>
- /// Specifies the Base32 character encoding
- /// </summary>
- Base32 = 32
- }
/// <summary>
/// Provides simple methods for common managed hashing functions
@@ -119,6 +69,19 @@ namespace VNLib.Hashing
};
}
+ private static readonly Sha1 _sha1Alg;
+ private static readonly Sha256 _sha256Alg;
+ private static readonly Sha384 _sha384Alg;
+ private static readonly Sha512 _sha512Alg;
+ private static readonly Md5 _md5Alg;
+ private static readonly Blake2b _blake2bAlg;
+
+ /// <summary>
+ /// 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();
+
/// <summary>
/// Uses the UTF8 character encoding to encode the string, then
/// attempts to compute the hash and store the results into the output buffer
@@ -130,16 +93,16 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static ERRNO ComputeHash(ReadOnlySpan<char> data, Span<byte> buffer, HashAlg type)
{
- int byteCount = CharEncoding.GetByteCount(data);
-
- //Alloc buffer
- using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
-
- //Encode data
- byteCount = CharEncoding.GetBytes(data, binbuf.Span);
-
- //hash the buffer
- return ComputeHash(binbuf.Span[..byteCount], buffer, type);
+ return type switch
+ {
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, buffer),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, buffer),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, buffer),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, buffer),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, buffer),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, buffer),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
+ };
}
/// <summary>
@@ -152,13 +115,16 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static byte[] ComputeHash(ReadOnlySpan<char> data, HashAlg type)
{
- int byteCount = CharEncoding.GetByteCount(data);
- //Alloc buffer
- using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
- //Encode data
- byteCount = CharEncoding.GetBytes(data, binbuf.Span);
- //hash the buffer
- return ComputeHash(binbuf.Span[..byteCount], type);
+ return type switch
+ {
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
+ };
}
/// <summary>
@@ -174,11 +140,12 @@ namespace VNLib.Hashing
//hash the buffer
return type switch
{
- HashAlg.SHA512 => SHA512.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA384 => SHA384.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA256 => SHA256.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA1 => SHA1.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.MD5 => MD5.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL,
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output),
_ => throw new ArgumentException("Hash algorithm is not supported"),
};
}
@@ -195,12 +162,13 @@ namespace VNLib.Hashing
//hash the buffer
return type switch
{
- HashAlg.SHA512 => SHA512.HashData(data),
- HashAlg.SHA384 => SHA384.HashData(data),
- HashAlg.SHA256 => SHA256.HashData(data),
- HashAlg.SHA1 => SHA1.HashData(data),
- HashAlg.MD5 => MD5.HashData(data),
- _ => throw new ArgumentException("Hash algorithm is not supported"),
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
};
}
@@ -216,21 +184,15 @@ namespace VNLib.Hashing
/// <exception cref="CryptographicException"></exception>
public static string ComputeHash(ReadOnlySpan<byte> data, HashAlg type, HashEncodingMode mode)
{
- //Alloc hash buffer
- Span<byte> hashBuffer = stackalloc byte[(int)type];
- //hash the buffer
- ERRNO count = ComputeHash(data, hashBuffer, type);
- if (!count)
- {
- throw new CryptographicException("Failed to compute the hash of the data");
- }
- //Convert to hex string
- return mode switch
+ return type switch
{
- HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)),
- _ => throw new ArgumentException("Encoding mode is not supported"),
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
};
}
@@ -247,30 +209,18 @@ namespace VNLib.Hashing
/// <exception cref="CryptographicException"></exception>
public static string ComputeHash(ReadOnlySpan<char> data, HashAlg type, HashEncodingMode mode)
{
- //Alloc hash buffer
- Span<byte> hashBuffer = stackalloc byte[(int)type];
- //hash the buffer
- ERRNO count = ComputeHash(data, hashBuffer, type);
- if (!count)
- {
- throw new CryptographicException("Failed to compute the hash of the data");
- }
- //Convert to hex string
- return mode switch
+ return type switch
{
- HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)),
- _ => throw new ArgumentException("Encoding mode is not supported"),
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
};
}
-
- public static string ComputeHexHash(ReadOnlySpan<byte> data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Hexadecimal);
- public static string ComputeBase64Hash(ReadOnlySpan<byte> data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Base64);
- public static string ComputeHexHash(ReadOnlySpan<char> data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Hexadecimal);
- public static string ComputeBase64Hash(ReadOnlySpan<char> data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Base64);
-
/// <summary>
/// Computes the HMAC of the specified character buffer using the specified key and
/// writes the resuts to the output buffer.
@@ -283,16 +233,16 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static ERRNO ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<char> data, Span<byte> output, HashAlg type)
{
- int byteCount = CharEncoding.GetByteCount(data);
-
- //Alloc buffer
- using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
-
- //Encode data
- byteCount = CharEncoding.GetBytes(data, binbuf.Span);
-
- //hash the buffer
- return ComputeHmac(key, binbuf.Span[..byteCount], output, type);
+ return type switch
+ {
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output, key),
+ _ => ERRNO.E_FAIL
+ };
}
/// <summary>
@@ -306,17 +256,18 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<char> data, HashAlg type)
{
- int byteCount = CharEncoding.GetByteCount(data);
-
- //Alloc buffer
- using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
-
- //Encode data
- byteCount = CharEncoding.GetBytes(data, binbuf.Span);
-
- //hash the buffer
- return ComputeHmac(key, binbuf.Span[..byteCount], type);
+ return type switch
+ {
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, key),
+ _ => throw new ArgumentException("Hash algorithm is not supported"),
+ };
}
+
/// <summary>
/// Computes the HMAC of the specified data buffer using the specified key and
/// writes the resuts to the output buffer.
@@ -332,11 +283,12 @@ namespace VNLib.Hashing
//hash the buffer
return type switch
{
- HashAlg.SHA512 => HMACSHA512.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA384 => HMACSHA384.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA256 => HMACSHA256.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.SHA1 => HMACSHA1.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL,
- HashAlg.MD5 => HMACMD5.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL,
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output, key),
_ => throw new ArgumentException("Hash algorithm is not supported"),
};
}
@@ -355,11 +307,12 @@ namespace VNLib.Hashing
//hash the buffer
return type switch
{
- HashAlg.SHA512 => HMACSHA512.HashData(key, data),
- HashAlg.SHA384 => HMACSHA384.HashData(key, data),
- HashAlg.SHA256 => HMACSHA256.HashData(key, data),
- HashAlg.SHA1 => HMACSHA1.HashData(key, data),
- HashAlg.MD5 => HMACMD5.HashData(key, data),
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, key),
_ => throw new ArgumentException("Hash algorithm is not supported"),
};
}
@@ -376,24 +329,15 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static string ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, HashAlg type, HashEncodingMode mode)
{
- //Alloc hash buffer
- Span<byte> hashBuffer = stackalloc byte[(int)type];
-
- //hash the buffer
- ERRNO count = ComputeHmac(key, data, hashBuffer, type);
-
- if (!count)
- {
- throw new InternalBufferTooSmallException("Failed to compute the hash of the data");
- }
-
- //Convert to hex string
- return mode switch
+ return type switch
{
- HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)),
- HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)),
- _ => throw new ArgumentException("Encoding mode is not supported"),
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode, key),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
};
}
@@ -409,25 +353,114 @@ namespace VNLib.Hashing
/// <exception cref="ArgumentException"></exception>
public static string ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<char> data, HashAlg type, HashEncodingMode mode)
{
- //Alloc hash buffer
- Span<byte> hashBuffer = stackalloc byte[(int)type];
+ return type switch
+ {
+ HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode, key),
+ HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode, key),
+ HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode, key),
+ HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode, key),
+ HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode, key),
+ HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode, key),
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
+ };
+ }
+
+ #region internal
+
+ private static byte[] ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<char> data, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ int byteCount = CharEncoding.GetByteCount(data);
+ //Alloc buffer
+ using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
+ //Encode data
+ byteCount = CharEncoding.GetBytes(data, binbuf.Span);
+ //hash the buffer
+ return ComputeHashInternal(in algorithm, binbuf.AsSpan(0, byteCount), key);
+ }
+
+ private static string ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<char> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ //Alloc stack buffer to store hash output
+ Span<byte> hashBuffer = stackalloc byte[algorithm.HashSize];
+ //hash the buffer
+ ERRNO count = ComputeHashInternal(in algorithm, data, hashBuffer, key);
+ if (!count)
+ {
+ throw new CryptographicException("Failed to compute the hash of the data");
+ }
+
+ //Convert to encoded string
+ return mode switch
+ {
+ HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)),
+ HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)),
+ HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)),
+ HashEncodingMode.Base64Url => VnEncoding.ToBase64UrlSafeString(hashBuffer.Slice(0, count), true),
+ _ => throw new ArgumentException("Encoding mode is not supported"),
+ };
+ }
+
+ private static string ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<byte> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ //Alloc stack buffer to store hash output
+ Span<byte> hashBuffer = stackalloc byte[algorithm.HashSize];
//hash the buffer
- ERRNO count = ComputeHmac(key, data, hashBuffer, type);
-
+ ERRNO count = ComputeHashInternal(in algorithm, data, hashBuffer, key);
if (!count)
{
- throw new InternalBufferTooSmallException("Failed to compute the hash of the data");
+ throw new CryptographicException("Failed to compute the hash of the data");
}
-
- //Convert to hex string
+
+ //Convert to encoded string
return mode switch
{
HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)),
HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)),
HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)),
+ HashEncodingMode.Base64Url => VnEncoding.ToBase64UrlSafeString(hashBuffer.Slice(0, count), true),
_ => throw new ArgumentException("Encoding mode is not supported"),
};
}
+
+ private static ERRNO ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<char> data, Span<byte> output, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ int byteCount = CharEncoding.GetByteCount(data);
+ //Alloc buffer
+ using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true);
+ //Encode data
+ byteCount = CharEncoding.GetBytes(data, binbuf.Span);
+ //hash the buffer or hmac if key is not empty
+ return ComputeHashInternal(in algorithm, binbuf.Span[..byteCount], output, key);
+ }
+
+
+ private static ERRNO ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<byte> data, Span<byte> buffer, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ //hash the buffer or hmac if key is not empty
+ if (key.IsEmpty)
+ {
+ return algorithm.TryComputeHash(data, buffer, out int written) ? written : ERRNO.E_FAIL;
+ }
+ else
+ {
+ return algorithm.TryComputeHmac(key, data, buffer, out int written) ? written : ERRNO.E_FAIL;
+ }
+ }
+
+ private static byte[] ComputeHashInternal<T>(in T algorithm, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key = default) where T : IHashAlgorithm
+ {
+ //hash the buffer or hmac if key is not empty
+ if (key.IsEmpty)
+ {
+ return algorithm.ComputeHash(data);
+ }
+ else
+ {
+ return algorithm.ComputeHmac(key, data);
+ }
+ }
+
+ #endregion
}
}
diff --git a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
new file mode 100644
index 0000000..7e2262e
--- /dev/null
+++ b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
@@ -0,0 +1,227 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: ManagedHashAlgImpl.cs
+*
+* ManagedHashAlgImpl.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+
+using VNLib.Utils;
+using VNLib.Hashing.Native.MonoCypher;
+
+namespace VNLib.Hashing
+{
+ public static partial class ManagedHash
+ {
+ private interface IHashAlgorithm
+ {
+ int HashSize { get; }
+
+ byte[] ComputeHash(ReadOnlySpan<byte> data);
+
+ bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count);
+
+ byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data);
+
+ bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count);
+ }
+
+ private readonly struct Sha1 : IHashAlgorithm
+ {
+ ///<inheritdoc/>
+ public readonly int HashSize => (int)HashAlg.SHA1;
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data) => SHA1.HashData(data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count) => SHA1.TryHashData(data, output, out count);
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data) => HMACSHA1.HashData(key, data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count) => HMACSHA1.TryHashData(key, data, output, out count);
+ }
+
+ private readonly struct Sha256 : IHashAlgorithm
+ {
+ ///<inheritdoc/>
+ public readonly int HashSize => (int)HashAlg.SHA256;
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data) => SHA256.HashData(data);
+
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count) => SHA256.TryHashData(data, output, out count);
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data) => HMACSHA256.HashData(key, data);
+
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count) => HMACSHA256.TryHashData(key, data, output, out count);
+ }
+
+ private readonly struct Sha384 : IHashAlgorithm
+ {
+ ///<inheritdoc/>
+ public readonly int HashSize => (int)HashAlg.SHA384;
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data) => SHA384.HashData(data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count) => SHA384.TryHashData(data, output, out count);
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data) => HMACSHA384.HashData(key, data);
+
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count) => HMACSHA384.TryHashData(key, data, output, out count);
+ }
+
+ private readonly struct Sha512 : IHashAlgorithm
+ {
+ ///<inheritdoc/>
+ public readonly int HashSize => (int)HashAlg.SHA512;
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data) => SHA512.HashData(data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count) => SHA512.TryHashData(data, output, out count);
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data) => HMACSHA512.HashData(key, data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count) => HMACSHA512.TryHashData(key, data, output, out count);
+ }
+
+ private readonly struct Md5 : IHashAlgorithm
+ {
+ ///<inheritdoc/>
+ public readonly int HashSize => (int)HashAlg.MD5;
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data) => MD5.HashData(data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count) => MD5.TryHashData(data, output, out count);
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data) => HMACMD5.HashData(key, data);
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count) => HMACMD5.TryHashData(key, data, output, out count);
+ }
+
+ private readonly struct Blake2b : IHashAlgorithm
+ {
+ const byte DefaultBlake2HashSize = 64;
+
+ internal static int MaxHashSize => MCBlake2Module.MaxHashSize;
+ internal static int MaxKeySize => MCBlake2Module.MaxKeySize;
+
+ ///<inheritdoc/>
+ public readonly int HashSize => DefaultBlake2HashSize;
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHash(ReadOnlySpan<byte> data)
+ {
+ //Stack buffer for output hash
+ byte[] output = new byte[DefaultBlake2HashSize];
+
+ if (!TryComputeHash(data, output, out int count))
+ {
+ throw new ArgumentException("Failed to compute Blake2 hash of desired data");
+ }
+
+ //Count must be exact same (sanity check)
+ Debug.Assert(count == DefaultBlake2HashSize);
+
+ //Return the hash as a new array
+ return output;
+ }
+
+ ///<inheritdoc/>
+ public readonly byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
+ {
+ //Alloc output buffer
+ byte[] output = new byte[DefaultBlake2HashSize];
+
+ if (!TryComputeHmac(key, data, output, out int count))
+ {
+ throw new ArgumentException("Failed to compute Blake2 hash of desired data");
+ }
+
+ //Count must be exact same (sanity check)
+ Debug.Assert(count == DefaultBlake2HashSize);
+
+ //Return the hash as a new array
+ return output;
+
+ }
+
+ ///<inheritdoc/>
+ public readonly bool TryComputeHash(ReadOnlySpan<byte> data, Span<byte> output, out int count)
+ {
+ if (output.Length > MCBlake2Module.MaxHashSize)
+ {
+ count = 0;
+ return false;
+ }
+
+ //Compute one-shot hash
+ ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHash(data, output);
+
+ if(result < output.Length)
+ {
+ count = 0;
+ return false;
+ }
+
+ count = output.Length;
+ return true;
+ }
+
+ ///<inheritdoc/>
+ public readonly bool TryComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output, out int count)
+ {
+ count = 0;
+
+ if (output.Length > MCBlake2Module.MaxHashSize)
+ {
+ return false;
+ }
+
+ //Test key size
+ if (key.Length > MCBlake2Module.MaxKeySize)
+ {
+ return false;
+ }
+
+ //Compute one-shot hash
+ ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output);
+
+ if (result < output.Length)
+ {
+ count = 0;
+ return false;
+ }
+
+ count = output.Length;
+ return true;
+ }
+ }
+ }
+}
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs b/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs
new file mode 100644
index 0000000..29def42
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs
@@ -0,0 +1,54 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: IHashStream.cs
+*
+* IHashStream.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+ /// <summary>
+ /// A base interface for a streaming (incremental) hash
+ /// function
+ /// </summary>
+ public interface IHashStream : IDisposable
+ {
+ /// <summary>
+ /// The configured hash size of this stream
+ /// </summary>
+ byte HashSize { get; }
+
+ /// <summary>
+ /// Updates the hash of this stream with the specified message
+ /// </summary>
+ /// <param name="mRef">A reference to the first byte of the sequence</param>
+ /// <param name="mSize">The size of the sequence</param>
+ void Update(ref byte mRef, uint mSize);
+
+ /// <summary>
+ /// Flushes the hash of this stream to the specified buffer
+ /// </summary>
+ /// <param name="hashOut">A reference to the first byte in the output sequence</param>
+ /// <param name="hashSize">The size of the output sequence</param>
+ void Flush(ref byte hashOut, byte hashSize);
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs b/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs
new file mode 100644
index 0000000..0ddc236
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: IHmacStream.cs
+*
+* IHmacStream.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+ /// <summary>
+ /// A base interface for a streaming (incremental) HMAC
+ /// authenticated hash function
+ /// </summary>
+ public interface IHmacStream: IHashStream
+ {
+ /// <summary>
+ /// The maximum key size allowed for this stream
+ /// </summary>
+ int MaxKeySize { get; }
+
+ /// <summary>
+ /// Initializes this stream with the specified key
+ /// </summary>
+ /// <param name="key">A reference to the first byte of key data to import</param>
+ /// <param name="keySize">The size of the key buffer</param>
+ /// <exception cref="System.ArgumentException"></exception>
+ void Initialize(ref byte key, byte keySize);
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs
new file mode 100644
index 0000000..e8f0156
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs
@@ -0,0 +1,420 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: MCBlake2Module.cs
+*
+* MCBlake2Module.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+ /// <summary>
+ /// Adds Blake2b hashing support to the <see cref="MonoCypherLibrary"/>
+ /// </summary>
+ public static unsafe class MCBlake2Module
+ {
+ [SafeMethodName("Blake2GetContextSize")]
+ internal delegate uint Blake2GetContextSize();
+
+ [SafeMethodName("Blake2Init")]
+ internal delegate int Blake2Init(IntPtr context, byte hashSize, void* key = null, uint keyLen = 0);
+
+ [SafeMethodName("Blake2Update")]
+ internal delegate int Blake2Update(IntPtr context, void* data, uint dataLen);
+
+ [SafeMethodName("Blake2Final")]
+ internal delegate int Blake2Final(IntPtr context, void* hash, uint hashLen);
+
+ [SafeMethodName("Blake2GetHashSize")]
+ internal delegate uint Blake2GetHashSize(IntPtr context);
+
+
+ public const int MaxHashSize = 64;
+ public const int MaxKeySize = 64;
+ public const int MinSuggestedKDFHashSize = 32;
+ public const int MinSuggestedMACHashSize = 16;
+
+ /// <summary>
+ /// Creates a new <see cref="IHashStream"/> instance with the specified hash size
+ /// which must be between 1 and <see cref="MaxHashSize"/> inclusive.
+ /// </summary>
+ /// <remarks>
+ /// A hash size greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="library"></param>
+ /// <param name="hashSize">The hash size between 1 and <see cref="MaxHashSize"/> inclusive</param>
+ /// <param name="heap">The heap to allocate the stream on</param>
+ /// <returns>The initialzied <see cref="IHashStream"/> instance</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static IHashStream Blake2CreateStream(this MonoCypherLibrary library, byte hashSize, IUnmangedHeap? heap)
+ {
+ _ = library ?? throw new ArgumentNullException(nameof(library));
+ if(hashSize == 0 || hashSize > MaxHashSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(hashSize), $"The hash size must be between 1 and {MaxHashSize} inclusive");
+ }
+ //Fall back to the shared heap if none is provided
+ heap ??= MemoryUtil.Shared;
+
+ //Alloc stream and initialize it as a non-hmac stream
+ Blake2Stream stream = new(library, heap, hashSize);
+ try
+ {
+ //Initialize the stream
+ stream.Initialize();
+ return stream;
+ }
+ catch
+ {
+ stream.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="IHmacStream"/> keyed MAC instance with the specified hash size
+ /// and key which must be between 1 and <see cref="MaxHashSize"/> inclusive. You must
+ /// initialize the instance before use, otherwise results are undefined.
+ /// </summary>
+ /// <remarks>
+ /// A hash size greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="library"></param>
+ /// <param name="hashSize">The hash size between 1 and <see cref="MaxHashSize"/> inclusive</param>
+ /// <param name="heap">The heap to allocate the stream on</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static IHmacStream Blake2CreateHmacStream(this MonoCypherLibrary library, byte hashSize, IUnmangedHeap? heap)
+ {
+ _ = library ?? throw new ArgumentNullException(nameof(library));
+ if (hashSize == 0 || hashSize > MaxHashSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(hashSize), $"The hash size must be between 1 and {MaxHashSize} inclusive");
+ }
+ //Fall back to the shared heap if none is provided
+ heap ??= MemoryUtil.Shared;
+
+ //Return the raw stream, it will be initialized later
+ return new Blake2Stream(library, heap, hashSize);
+ }
+
+ /// <summary>
+ /// Computes a Blake2b one-shot hash of the specified data and writes it to the variable-length output buffer.
+ /// The output buffer must be between 1 and <see cref="MaxHashSize"/> inclusive.
+ /// <para>
+ /// See <see href="https://monocypher.org/manual/blake2b"></see> for more information.
+ /// </para>
+ /// </summary>
+ /// <param name="library"></param>
+ /// <param name="data">The data buffer to compute the hash of</param>
+ /// <param name="output">The hash output buffer</param>
+ /// <returns>The number of bytes written to the output buffer or the error code from the native library</returns>
+ public static ERRNO Blake2ComputeHash(this MonoCypherLibrary library, ReadOnlySpan<byte> data, Span<byte> output)
+ {
+ ArgumentNullException.ThrowIfNull(library, nameof(library));
+
+ if(output.Length > MaxHashSize)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ if(output.IsEmpty)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Get context size
+ int contextSize = (int)library.Functions.Blake2GetContextSize();
+
+ //Allocate context on the stack
+ void* ctx = stackalloc byte[contextSize];
+
+ //Init context
+ if(library.Functions.Blake2Init((IntPtr)ctx, (byte)output.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //initialize the context for the stream with hmac key
+ fixed (byte* keyPtr = &MemoryMarshal.GetReference(data),
+ hashPtr = &MemoryMarshal.GetReference(output)
+ )
+ {
+ //Update with data
+ if(library.Functions.Blake2Update((IntPtr)ctx, keyPtr, (uint)data.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+ //Copy hash to output
+ if (library.Functions.Blake2Final((IntPtr)ctx, hashPtr, (uint)output.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+
+ return output.Length;
+ }
+
+ /// <summary>
+ /// Computes a Blake2b one-shot keyed MAC of the specified data and writes it to the variable-length output buffer.
+ /// The output buffer must be between 1 and <see cref="MaxHashSize"/> inclusive.
+ /// The key must be between 1 and <see cref="MaxKeySize"/> inclusive.
+ /// <para>
+ /// See <see href="https://monocypher.org/manual/blake2b"></see> for more information.
+ /// </para>
+ /// </summary>
+ /// <param name="library"></param>
+ /// <param name="key">The HMAC key</param>
+ /// <param name="data">The data buffer to compute the hash of</param>
+ /// <param name="output">The hash output buffer</param>
+ /// <returns>The number of bytes written to the output buffer or the error code from the native library</returns>
+ public static ERRNO Blake2ComputeHmac(this MonoCypherLibrary library, ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> output)
+ {
+ ArgumentNullException.ThrowIfNull(library, nameof(library));
+
+ if (output.Length > MaxHashSize)
+ {
+ return ERR_HASH_LEN_INVLID;
+ }
+
+ if (output.IsEmpty)
+ {
+ return ERR_NULL_PTR;
+ }
+
+ if(key.IsEmpty)
+ {
+ return ERR_KEY_PTR_INVALID;
+ }
+
+ if(key.Length > MaxKeySize)
+ {
+ return ERR_KEY_LEN_INVALID;
+ }
+
+ //Get context size
+ int contextSize = (int)library.Functions.Blake2GetContextSize();
+
+ //Allocate context on the stack
+ void* ctx = stackalloc byte[contextSize];
+
+ fixed(byte* keyPtr = &MemoryMarshal.GetReference(key))
+ {
+ //Init context with hmac parameters
+ if (library.Functions.Blake2Init((IntPtr)ctx, (byte)output.Length, keyPtr, (uint)key.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+
+ //initialize the context for the stream with hmac key
+ fixed (byte* keyPtr = &MemoryMarshal.GetReference(data))
+ {
+ //Update with data
+ if (library.Functions.Blake2Update((IntPtr)ctx, keyPtr, (uint)data.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+
+ //Finalize/copy the current hash
+ fixed (byte* hashPtr = &MemoryMarshal.GetReference(output))
+ {
+ if (library.Functions.Blake2Final((IntPtr)ctx, hashPtr, (uint)output.Length) != 0)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+
+ //Clear the context before returning
+ MemoryUtil.InitializeBlock((byte*)ctx, contextSize);
+
+ return output.Length;
+ }
+
+ const int ERR_NULL_PTR = -1;
+ const int ERR_HASH_LEN_INVLID = -16;
+ const int ERR_KEY_LEN_INVALID = -17;
+ const int ERR_KEY_PTR_INVALID = -18;
+
+ private static void ThrowOnBlake2Error(int result)
+ {
+ switch (result)
+ {
+ //Success
+ case 0:
+ break;
+ //Null pointer
+ case ERR_NULL_PTR:
+ throw new ArgumentException("An illegal null pointer was passed to the function");
+ //Invalid hash length
+ case ERR_HASH_LEN_INVLID:
+ throw new ArgumentOutOfRangeException("hashLen", "The hash length is invalid");
+ //Invalid key length
+ case ERR_KEY_LEN_INVALID:
+ throw new ArgumentOutOfRangeException("keyLen", "The key length is invalid");
+
+ case ERR_KEY_PTR_INVALID:
+ throw new ArgumentException("The key pointer is null");
+
+ default:
+ throw new Exception($"An unknown error occured while hashing: {result}");
+
+ }
+ }
+
+ private sealed class Blake2Stream : SafeHandle, IHashStream, IHmacStream
+ {
+ private readonly MonoCypherLibrary _library;
+ private readonly IUnmangedHeap _heap;
+
+ ///<inheritdoc/>
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ internal Blake2Stream(MonoCypherLibrary library, IUnmangedHeap heap, byte hashSize) :base(IntPtr.Zero, true)
+ {
+ Debug.Assert(hashSize > 0 && hashSize <= MaxHashSize, "Hash size must be between 1 and 64 inclusive");
+ Debug.Assert(library != null, "Library argument passed to internal blake2 stream constructur is null");
+ _library = library;
+ _heap = heap;
+ HashSize = hashSize;
+ }
+
+ internal void Initialize()
+ {
+ //Make sure context is initialized
+ InitContextHandle();
+
+ //Init non-hmac
+ int initResult = _library.Functions.Blake2Init(handle, HashSize);
+ ThrowOnBlake2Error(initResult);
+ }
+
+ ///<inheritdoc/>
+ public byte HashSize { get; }
+
+ ///<inheritdoc/>
+ public int MaxKeySize => MaxKeySize;
+
+ ///<inheritdoc/>
+ public void Flush(ref byte hashOut, byte hashSize)
+ {
+ this.ThrowIfClosed();
+
+ if(Unsafe.IsNullRef(ref hashOut))
+ {
+ throw new ArgumentNullException(nameof(hashOut));
+ }
+
+ //Guard for hash size
+ if(hashSize != HashSize)
+ {
+ throw new ArgumentException("The hash output must be the configured hash size", nameof(hashSize));
+ }
+ //get the address of our context and the hash output reference
+
+ fixed(byte* hashOutPtr = &hashOut)
+ {
+ _library.Functions.Blake2Final(handle, hashOutPtr, hashSize);
+ }
+ }
+
+ ///<inheritdoc/>
+ public void Initialize(ref byte key, byte keySize)
+ {
+ if (Unsafe.IsNullRef(ref key))
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if(keySize > MaxKeySize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(keySize), $"The key size must be between 1 and {MaxKeySize} inclusive bytes");
+ }
+
+ //Make sure context is initialized
+ InitContextHandle();
+
+ //initialize the context for the stream with hmac key
+ fixed (byte* keyPtr = &key)
+ {
+ int result = _library.Functions.Blake2Init(handle, HashSize, keyPtr, keySize);
+ ThrowOnBlake2Error(result);
+ }
+ }
+
+ ///<inheritdoc/>
+ public void Update(ref byte mRef, uint mSize)
+ {
+ this.ThrowIfClosed();
+
+ if (Unsafe.IsNullRef(ref mRef))
+ {
+ throw new ArgumentNullException(nameof(mRef));
+ }
+
+ if (mSize == 0)
+ {
+ return;
+ }
+
+ //get the address of the message reference
+ fixed (byte* message = &mRef)
+ {
+ int result = _library.Functions.Blake2Update(handle, message, mSize);
+ ThrowOnBlake2Error(result);
+ }
+ }
+
+ private void InitContextHandle()
+ {
+ if (IsClosed)
+ {
+ throw new ObjectDisposedException(nameof(Blake2Stream));
+ }
+
+ //alloc buffer on the heap if not allocated
+ if (handle == IntPtr.Zero)
+ {
+ handle = _heap.Alloc(1, _library.Functions.Blake2GetContextSize(), true);
+ }
+ }
+
+ ///<inheritdoc/>
+ protected override bool ReleaseHandle() => _heap.Free(ref handle);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs
new file mode 100644
index 0000000..3f27539
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs
@@ -0,0 +1,292 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: MCHashingStreamExtensions.cs
+*
+* MCHashingStreamExtensions.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+ /// <summary>
+ /// Provides extension methods for <see cref="IHashStream"/> instances
+ /// </summary>
+ public static unsafe class MCHashingStreamExtensions
+ {
+ /// <summary>
+ /// Updates the hash of this stream with the specified message
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="message">A pointer to the message memory sequence</param>
+ /// <param name="mSize">The size of the sequence</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Update(this IHashStream hashStream, IntPtr message, uint mSize) => Update(hashStream, message.ToPointer(), mSize);
+
+ /// <summary>
+ /// Updates the hash of this stream with the specified message
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="message">A pointer to the message memory sequence</param>
+ /// <param name="mSize">The size of the sequence</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Update(this IHashStream hashStream, void* message, uint mSize)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+
+ //get ref from pointer
+ ref byte mRef = ref Unsafe.AsRef<byte>(message);
+ hashStream.Update(ref mRef, mSize);
+ }
+
+ /// <summary>
+ /// Updates the hash of this stream with the specified message
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="message">The message memory sequence</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Update(this IHashStream hashStream, ReadOnlySpan<byte> message)
+ {
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+
+ if(message.Length == 0)
+ {
+ return;
+ }
+
+ //Marshal reference to span
+ ref byte mRef = ref MemoryMarshal.GetReference(message);
+ hashStream.Update(ref mRef, (uint)message.Length);
+ }
+
+ /// <summary>
+ /// Updates the hash of this stream with the specified message
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="message">The message memory sequence</param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Update(this IHashStream hashStream, ReadOnlyMemory<byte> message)
+ {
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+ if (message.Length == 0)
+ {
+ return;
+ }
+
+ //Pin memory block instead of span marshalling
+ using MemoryHandle mHandle = message.Pin();
+
+ hashStream.Update(mHandle.Pointer, (uint)message.Length);
+ }
+
+ /// <summary>
+ /// Writes the internal hash state to the specified memory location. The hash size
+ /// must be at least the size of <see cref="IHashStream.HashSize"/>
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="hashOut">A pointer to the memory sequence to write the hash to</param>
+ /// <param name="hashSize">The size of the hash memory sequence, must be exactly <see cref="IHashStream.HashSize"/></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Flush(this IHashStream hashStream, IntPtr hashOut, byte hashSize) => Flush(hashStream, hashOut.ToPointer(), hashSize);
+
+ /// <summary>
+ /// Writes the internal hash state to the specified memory location. The hash size
+ /// must be at least the size of <see cref="IHashStream.HashSize"/>
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="hashOut">A pointer to the memory sequence to write the hash to</param>
+ /// <param name="hashSize">The size of the hash memory sequence, must be exactly <see cref="IHashStream.HashSize"/></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Flush(this IHashStream hashStream, void* hashOut, byte hashSize)
+ {
+ if (hashOut == null)
+ {
+ throw new ArgumentNullException(nameof(hashOut));
+ }
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+
+ //get ref from pointer
+ ref byte hashOutRef = ref Unsafe.AsRef<byte>(hashOut);
+ hashStream.Flush(ref hashOutRef, hashSize);
+ }
+
+ /// <summary>
+ /// Writes the internal hash state to the specified memory location. The hash size
+ /// must be at least the size of <see cref="IHashStream.HashSize"/>
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="hashOut">The memory sequence to write the hash to, must be exactly <see cref="IHashStream.HashSize"/></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Flush(this IHashStream hashStream, Span<byte> hashOut)
+ {
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+
+ if (hashOut.Length != hashStream.HashSize)
+ {
+ throw new ArgumentException("The hash output must be the configured hash size", nameof(hashOut));
+ }
+
+ //Marshal reference to span and flush
+ ref byte hashOutRef = ref MemoryMarshal.GetReference(hashOut);
+ hashStream.Flush(ref hashOutRef, (byte)hashOut.Length);
+ }
+
+ /// <summary>
+ /// Writes the internal hash state to the specified memory location. The hash size
+ /// must be at least the size of <see cref="IHashStream.HashSize"/>
+ /// </summary>
+ /// <param name="hashStream"></param>
+ /// <param name="hashOut">The memory sequence to write the hash to, must be exactly <see cref="IHashStream.HashSize"/></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Flush(this IHashStream hashStream, Memory<byte> hashOut)
+ {
+ _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream));
+
+ if (hashOut.Length != hashStream.HashSize)
+ {
+ throw new ArgumentException("The hash output must be the configured hash size", nameof(hashOut));
+ }
+
+ //Pin memory block instead of span marshalling
+ using MemoryHandle hashOutHandle = hashOut.Pin();
+ hashStream.Flush(hashOutHandle.Pointer, (byte)hashOut.Length);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="IHmacStream"/> keyed MAC instance with the specified hash size.
+ /// </summary>
+ /// <remarks>
+ /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="stream"></param>
+ /// <param name="key">A pointer to the HMAC key buffer</param>
+ /// <param name="keySize">The HMAC key must be between 1 and <see cref="IHmacStream.MaxKeySize"/> inclusive</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Initialize(this IHmacStream stream, IntPtr key, byte keySize) => Initialize(stream, (byte*)key.ToPointer(), keySize);
+
+ /// <summary>
+ /// Creates a new <see cref="IHmacStream"/> keyed MAC instance with the specified hash size.
+ /// </summary>
+ /// <remarks>
+ /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="stream"></param>
+ /// <param name="key">A pointer to the HMAC key buffer</param>
+ /// <param name="keySize">The HMAC key must be between 1 and <see cref="IHmacStream.MaxKeySize"/> inclusive</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Initialize(this IHmacStream stream, byte* key, byte keySize)
+ {
+ _ = stream ?? throw new ArgumentNullException(nameof(stream));
+
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+ if (keySize == 0 || keySize > stream.MaxKeySize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(keySize), $"The key size must be between 1 and {stream.MaxKeySize} inclusive");
+ }
+
+ //Get reference to key
+ ref byte asRef = ref Unsafe.AsRef<byte>(key);
+ stream.Initialize(ref asRef, keySize);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="IHmacStream"/> keyed MAC instance with the specified hash size.
+ /// </summary>
+ /// <remarks>
+ /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="stream"></param>
+ /// <param name="key">The HMAC key buffer</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Initialize(this IHmacStream stream, ReadOnlySpan<byte> key)
+ {
+ _ = stream ?? throw new ArgumentNullException(nameof(stream));
+
+ if (key.Length > stream.MaxKeySize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(key), $"The hash size must be less than or equal to {stream.MaxKeySize}");
+ }
+
+ //Get span ref and call interface method
+ ref byte asRef = ref MemoryMarshal.GetReference(key);
+ stream.Initialize(ref asRef, (byte)key.Length);
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="IHmacStream"/> keyed MAC instance with the specified hash size.
+ /// </summary>
+ /// <remarks>
+ /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/blake2b"/>
+ /// </para>
+ /// </remarks>
+ /// <param name="stream"></param>
+ /// <param name="key">The HMAC key buffer</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Initialize(this IHmacStream stream, ReadOnlyMemory<byte> key)
+ {
+ _ = stream ?? throw new ArgumentNullException(nameof(stream));
+
+ if (key.Length > stream.MaxKeySize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(key), $"The hash size must be less than or equal to {stream.MaxKeySize}");
+ }
+
+ //If key is default, then h.Pointer will be null, which is handled
+ using MemoryHandle h = key.Pin();
+
+ //Get address of ref
+ Initialize(stream, (byte*)h.Pointer, (byte)key.Length);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs
new file mode 100644
index 0000000..9dbcb41
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs
@@ -0,0 +1,159 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: MCPasswordModule.cs
+*
+* MCPasswordModule.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+
+ /// <summary>
+ /// Provides argon2 hashing functionality using the MonoCypher library.
+ /// <para>
+ /// <seealso href="https://monocypher.org/manual/argon2"/>
+ /// </para>
+ /// </summary>
+ public static unsafe class MCPasswordModule
+ {
+
+ const uint MC_ARGON2_WA_MEM_ALIGNMENT = sizeof(ulong);
+ const ulong MC_BLOCK_MULTIPLIER = 1024UL;
+
+ [SafeMethodName("Argon2CalcWorkAreaSize")]
+ internal delegate uint Argon2CalcWorkArea(Argon2Context* context);
+
+ [SafeMethodName("Argon2ComputeHash")]
+ internal delegate Argon2_ErrorCodes Argon2Hash(Argon2Context* context, void* workArea);
+
+
+ /// <summary>
+ /// Creates a new <see cref="IArgon2Library"/> instance using the provided <paramref name="heap"/>.
+ /// </summary>
+ /// <param name="Library"></param>
+ /// <param name="heap">The heap to allocate internal buffers from</param>
+ /// <returns>The <see cref="IArgon2Library"/> wrapper instance</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static IArgon2Library Argon2CreateLibrary(this MonoCypherLibrary Library, IUnmangedHeap heap)
+ {
+ //Validate arguments
+ _ = Library ?? throw new ArgumentNullException(nameof(Library));
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ return new Argon2HashLib(Library, heap);
+ }
+
+ private static void Hash(this MonoCypherLibrary library, IUnmangedHeap heap, Argon2Context* context)
+ {
+ _ = library ?? throw new ArgumentNullException(nameof(library));
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+
+ //Validate context
+ ValidateContext(context);
+
+ CalcWorkAreaSize(context, out uint elements, out uint alignment);
+
+ //Allocate work area
+ IntPtr workArea = heap.Alloc(elements, alignment, true);
+
+ try
+ {
+ //Compute the hash using the allocated work area
+ Argon2_ErrorCodes result = library.Functions.Argon2Hash(context, workArea.ToPointer());
+ VnArgon2.ThrowOnArgonErr(result);
+ }
+ finally
+ {
+ //Free work area
+ bool free = heap.Free(ref workArea);
+ Debug.Assert(free, "Failed to free work area pointer after allocation");
+ }
+ }
+
+ /*
+ * Since unmanaged heaps are being utilized and they support alignment args, we can compute
+ * a proper alignment value and element count for the work area that best matches the native
+ * impl.
+ *
+ * Currently the blocks are broken into a struct array of 128 u64 elements. So currently
+ * using sizeof(ulong) as the alignment value so we can pass that to the heap for better
+ * alignment
+ */
+
+ private static void CalcWorkAreaSize(Argon2Context* ctx, out uint elements, out uint alignment)
+ {
+ ulong mCost = ctx->m_cost;
+ ulong size = mCost * MC_BLOCK_MULTIPLIER;
+
+ //Calculate element size after alignment
+ elements = checked((uint)(size / MC_ARGON2_WA_MEM_ALIGNMENT));
+ alignment = MC_ARGON2_WA_MEM_ALIGNMENT;
+
+ //Sanity check
+ Debug.Assert(((ulong)elements * (ulong)alignment) == size);
+ }
+
+ private static void ValidateContext(Argon2Context* context)
+ {
+ if(context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (context->outptr == null || context->outlen == 0)
+ {
+ throw new ArgumentException("Output buffer is null or empty");
+ }
+
+ if (context->pwd == null || context->pwdlen == 0)
+ {
+ throw new ArgumentException("Password buffer is null or empty");
+ }
+
+ if (context->salt == null || context->saltlen == 0)
+ {
+ throw new ArgumentException("Salt buffer is null or empty");
+ }
+ }
+
+ private sealed record class Argon2HashLib(MonoCypherLibrary Library, IUnmangedHeap BufferHeap) : IArgon2Library
+ {
+
+ ///<inheritdoc/>
+ public int Argon2Hash(IntPtr context)
+ {
+ if(context == IntPtr.Zero)
+ {
+ throw new ArgumentNullException(nameof(context), "");
+ }
+
+ //Invoke hash with argon2 context pointer
+ Hash(Library, BufferHeap, (Argon2Context*)context);
+ return 0;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs
new file mode 100644
index 0000000..ff517ef
--- /dev/null
+++ b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs
@@ -0,0 +1,169 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: MonoCypherLibrary.cs
+*
+* MonoCypherLibrary.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using VNLib.Utils;
+using VNLib.Utils.Native;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Hashing.Native.MonoCypher
+{
+
+ /// <summary>
+ /// Wraps a safe library handle to the MonoCypher library and
+ /// provides access to the MonoCypher functions
+ /// </summary>
+ public unsafe class MonoCypherLibrary : VnDisposeable
+ {
+ public const string MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_MONOCYPHER_DLL_PATH";
+ public const string MONOCYPHER_LIB_DEFAULT_NAME = "vnlib_monocypher";
+
+ /// <summary>
+ /// Determines if the default MonoCypher library can be loaded into
+ /// the current process.
+ /// </summary>
+ /// <returns>true if the user enabled the default library, false otherwise</returns>
+ public static bool CanLoadDefaultLibrary() => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME)) == false;
+
+ private static readonly Lazy<MonoCypherLibrary> _defaultLib = new (LoadDefaultLibraryInternal, LazyThreadSafetyMode.PublicationOnly);
+
+ /// <summary>
+ /// Gets the default MonoCypher library for the current process
+ /// <para>
+ /// You should call <see cref="CanLoadDefaultLibrary"/> before accessing
+ /// this property to ensure that the default library can be loaded
+ /// </para>
+ /// </summary>
+ public static MonoCypherLibrary Shared => _defaultLib.Value;
+
+ /// <summary>
+ /// Loads a new instance of the MonoCypher library with environment defaults
+ /// <para>
+ /// You should call <see cref="CanLoadDefaultLibrary"/> before calling this
+ /// function
+ /// </para>
+ /// </summary>
+ /// <returns>The new library instance</returns>
+ /// <exception cref="DllNotFoundException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ public static MonoCypherLibrary LoadNewInstance() => LoadDefaultLibraryInternal();
+
+ /// <summary>
+ /// Loads the MonoCypher library from the specified shared library path
+ /// </summary>
+ /// <param name="path">The file path or library name to search for</param>
+ /// <param name="searchPath">The directory search flags</param>
+ /// <returns>The new <see cref="MonoCypherLibrary"/> instance</returns>
+ public static MonoCypherLibrary LoadLibrary(string path, DllImportSearchPath searchPath)
+ {
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(path, searchPath);
+ return new(lib, true);
+ }
+
+ private static MonoCypherLibrary LoadDefaultLibraryInternal()
+ {
+ //Get the path to the library or default
+ string? monoCypherLibPath = Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME) ?? MONOCYPHER_LIB_DEFAULT_NAME;
+
+ Trace.WriteLine("Attempting to load global native MonoCypher library from: " + monoCypherLibPath, "MonoCypher");
+
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(monoCypherLibPath, DllImportSearchPath.SafeDirectories);
+ return new(lib, true);
+ }
+
+
+ private readonly SafeLibraryHandle _library;
+ private readonly FunctionTable _functions;
+ private readonly bool _ownsHandle;
+
+ internal ref readonly FunctionTable Functions
+ {
+ get
+ {
+ Check();
+ return ref _functions;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="MonoCypherLibrary"/> consuming the
+ /// specified library handle
+ /// </summary>
+ /// <param name="library">The safe MonoCypher library handle</param>
+ /// <param name="ownsHandle">A value that indicates if the current instance owns the library handle</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public MonoCypherLibrary(SafeLibraryHandle library, bool ownsHandle)
+ {
+ _library = library ?? throw new ArgumentNullException(nameof(library));
+ _ownsHandle = ownsHandle;
+
+ //Init the function table
+ InitFunctionTable(library, out _functions);
+ }
+
+ private static void InitFunctionTable(SafeLibraryHandle library, out FunctionTable functions)
+ {
+ functions = new FunctionTable
+ {
+ //Argon2
+ Argon2Hash = library.DangerousGetMethod<MCPasswordModule.Argon2Hash>(),
+ Argon2CalcWorkArea = library.DangerousGetMethod<MCPasswordModule.Argon2CalcWorkArea>(),
+
+ //Blake2b
+ Blake2GetContextSize = library.DangerousGetMethod<MCBlake2Module.Blake2GetContextSize>(),
+ Blake2Init = library.DangerousGetMethod<MCBlake2Module.Blake2Init>(),
+ Blake2Update = library.DangerousGetMethod<MCBlake2Module.Blake2Update>(),
+ Blake2Final = library.DangerousGetMethod<MCBlake2Module.Blake2Final>(),
+ Blake2GethashSize = library.DangerousGetMethod<MCBlake2Module.Blake2GetHashSize>(),
+ };
+ }
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ if(_ownsHandle)
+ {
+ _library.Dispose();
+ }
+ }
+
+ internal readonly struct FunctionTable
+ {
+ //Argon2 module
+ public readonly MCPasswordModule.Argon2Hash Argon2Hash { get; init; }
+ public readonly MCPasswordModule.Argon2CalcWorkArea Argon2CalcWorkArea { get; init; }
+
+ //Blake2 module
+ public readonly MCBlake2Module.Blake2GetContextSize Blake2GetContextSize { get; init; }
+ public readonly MCBlake2Module.Blake2Init Blake2Init { get; init; }
+ public readonly MCBlake2Module.Blake2Update Blake2Update { get; init; }
+ public readonly MCBlake2Module.Blake2Final Blake2Final { get; init; }
+ public readonly MCBlake2Module.Blake2GetHashSize Blake2GethashSize { get; init; }
+ }
+ }
+} \ No newline at end of file