diff --git a/readme.md b/readme.md index 35a45c6..8f15b58 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ Blake2Fast ========== -These [RFC 7693](https://tools.ietf.org/html/rfc7693)-compliant Blake2 implementations have been tuned for high speed and low memory usage. The .NET Core 2.1 build supports the new X86 SIMD Intrinsics for even greater speed and `Span` for even lower memory usage. +These [RFC 7693](https://tools.ietf.org/html/rfc7693)-compliant BLAKE2 implementations have been tuned for high speed and low memory usage. The .NET Core 2.1 build supports the new X86 SIMD Intrinsics for even greater speed and `Span` for even lower memory usage. Sample benchmark results comparing with built-in .NET algorithms, 10MiB input, .NET Core 2.1 x64 and x86 runtimes: @@ -19,7 +19,7 @@ MD5 | X86 | 20.06 ms | 0.0996 ms | 0.0931 ms | 0 B | SHA256 | X86 | 52.47 ms | 0.3252 ms | 0.3042 ms | 0 B | SHA512 | X86 | 44.07 ms | 0.1643 ms | 0.1372 ms | 0 B | -You can find more detailed comparison between Blake2Fast and other .NET Blake2 implementations starting [here](https://photosauce.net/blog/post/fast-hashing-with-blake2-part-1-nuget-is-a-minefield) +You can find more detailed comparison between Blake2Fast and other .NET BLAKE2 implementations starting [here](https://photosauce.net/blog/post/fast-hashing-with-blake2-part-1-nuget-is-a-minefield) Installation ------------ @@ -36,26 +36,29 @@ Usage ### All-at-Once Hashing The simplest and lightest-weight way to calculate a hash is the all-at-once `ComputeHash` method. + ```C# -Blake2b.ComputeHash(data); +var hash = Blake2b.ComputeHash(data); ``` -Blake2 supports variable digest lengths from 1 to 32 bytes for `Blake2s` or 1 to 64 bytes for `Blake2b`. +BLAKE2 supports variable digest lengths from 1 to 32 bytes for BLAKE2s or 1 to 64 bytes for BLAKE2b. + ```C# -Blake2b.ComputeHash(48, data); +var hash = Blake2b.ComputeHash(42, data); ``` -Blake2 also natively supports keyed hashing. +BLAKE2 also natively supports keyed hashing. + ```C# -Blake2b.ComputeHash(key, data); +var hash = Blake2b.ComputeHash(key, data); ``` ### Incremental Hashing -Blake2 hashes can be incrementally updated if you do not have the data available all at once. +BLAKE2 hashes can be incrementally updated if you do not have the data available all at once. ```C# -async Task CalculateHashAsync(Stream data) +async Task ComputeHashAsync(Stream data) { var incHash = Blake2b.CreateIncrementalHasher(); var buffer = new byte[4096]; @@ -71,11 +74,30 @@ async Task CalculateHashAsync(Stream data) } ``` +### Allocation-Free Hashing + +The output hash digest can be written to an existing buffer to avoid allocating a new array each time. This is especially useful when performing an iterative hash, as might be used in a [key derivation function](https://en.wikipedia.org/wiki/Key_derivation_function). + +```C# +byte[] DeriveBytes(string password, byte[] salt) +{ + // Create key from password, then hash the salt using the key + var pwkey = Blake2b.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password)); + var hbuff = Blake2b.ComputeHash(pwkey, salt); + + // Hash the hash lots of times, re-using the same buffer + for (int i = 0; i < 1_000_000; i++) + Blake2b.ComputeAndWriteHash(pwkey, hbuff, hbuff); + + return hbuff; +} +``` + ### System.Security.Cryptography Interop For interoperating with code that uses `System.Security.Cryptography` primitives, Blake2Fast can create a `HashAlgorithm` wrapper. The wrapper inherits from `HMAC` in case keyed hashing is required. -`HashAlgorithm` is less efficient than the above methods, so use it only when necessary. +`HashAlgorithm` is less efficient than the above methods, so use it only when necessary for compatibility. ```C# byte[] WriteDataAndCalculateHash(byte[] data) diff --git a/src/Blake2Fast/Blake2Fast.csproj b/src/Blake2Fast/Blake2Fast.csproj index 5202784..6d2d39d 100644 --- a/src/Blake2Fast/Blake2Fast.csproj +++ b/src/Blake2Fast/Blake2Fast.csproj @@ -6,35 +6,32 @@ Blake2Fast netstandard1.0;netstandard1.3;netstandard2.0;netcoreapp2.0;netcoreapp2.1;net45 true - pdbonly - true latest - 0.1.0 + 0.2.0 Clinton Ingram Blake2Fast - Optimized implementations of the Blake2b and Blake2s hashing algorithms. Uses SSE2-SSE4.1 intrinsics support on .NET Core 2.1 + Optimized implementations of the BLAKE2b and BLAKE2s hashing algorithms. Uses SSE2-SSE4.1 Intrinsics support on .NET Core 2.1 Copyright © 2018 Clinton Ingram git https://github.com/saucecontrol/Blake2Fast https://photosauce.net/icon64x64.png https://github.com/saucecontrol/Blake2Fast - https://github.com/saucecontrol/Blake2Fast/license + https://github.com/saucecontrol/Blake2Fast/blob/master/license See https://github.com/saucecontrol/Blake2Fast/releases - Blake2 Hash Blake2b Blake2s SSE SIMD HashAlgorithm HMAC - True + BLAKE2 Hash BLAKE2b BLAKE2s SSE SIMD HashAlgorithm HMAC $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - $(DefineConstants);IMPLICIT_BYTESPAN - - - - $(DefineConstants);FAST_SPAN;USE_INTRINSICS + + $(DefineConstants);IMPLICIT_BYTESPAN + $(DefineConstants);FAST_SPAN;USE_INTRINSICS - bin\$(Configuration)\$(TargetFrameWork)\SauceControl.Blake2Fast.xml + pdbonly + true + True + bin\$(Configuration)\$(TargetFrameWork)\$(AssemblyName).xml diff --git a/src/Blake2Fast/Blake2b.cs b/src/Blake2Fast/Blake2b.cs index 33eafe0..2c79163 100644 --- a/src/Blake2Fast/Blake2b.cs +++ b/src/Blake2Fast/Blake2b.cs @@ -1,28 +1,35 @@ -#if !NETSTANDARD1_0 +using System; + +#if !NETSTANDARD1_0 using System.Security.Cryptography; #endif #if FAST_SPAN using ByteSpan = System.ReadOnlySpan; +using WriteableByteSpan = System.Span; #else using ByteSpan = System.ArraySegment; +using WriteableByteSpan = System.ArraySegment; #endif namespace SauceControl.Blake2Fast { - /// Static helper methods for Blake2b hashing. + /// Static helper methods for BLAKE2b hashing. public static class Blake2b { + /// The default hash digest length in bytes. For BLAKE2b, this value is 64. + public const int DefaultDigestLength = Blake2bContext.HashBytes; + /// - public static byte[] ComputeHash(ByteSpan input) => ComputeHash(Blake2bContext.HashBytes, default, input); + public static byte[] ComputeHash(ByteSpan input) => ComputeHash(DefaultDigestLength, default, input); /// public static byte[] ComputeHash(int digestLength, ByteSpan input) => ComputeHash(digestLength, default, input); /// - public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(Blake2bContext.HashBytes, key, input); + public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(DefaultDigestLength, key, input); - /// Perform an all-at-once Blake2b hash computation. + /// Perform an all-at-once BLAKE2b hash computation. /// If you have all the input available at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to 64. /// 0 to 64 bytes of input for initializing a keyed hash. @@ -36,16 +43,50 @@ public static byte[] ComputeHash(int digestLength, ByteSpan key, ByteSpan input) return ctx.Finish(); } + /// + public static void ComputeAndWriteHash(ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, default, input, output); + + /// + public static void ComputeAndWriteHash(int digestLength, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(digestLength, default, input, output); + + /// Perform an all-at-once BLAKE2b hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// 0 to 64 bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least (64) /> bytes. + public static void ComputeAndWriteHash(ByteSpan key, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, key, input, output); + + /// Perform an all-at-once BLAKE2b hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// The hash digest length in bytes. Valid values are 1 to 64. + /// 0 to 64 bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least bytes. + public static void ComputeAndWriteHash(int digestLength, ByteSpan key, ByteSpan input, WriteableByteSpan output) + { +#if FAST_SPAN + if (output.Length < digestLength) +#else + if (output.Count < digestLength) +#endif + throw new ArgumentException($"Output buffer must have a capacity of at least {digestLength} bytes.", nameof(output)); + + var ctx = default(Blake2bContext); + ctx.Init(digestLength, key); + ctx.Update(input); + ctx.TryFinish(output, out int _); + } + /// - public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(Blake2bContext.HashBytes, default(ByteSpan)); + public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(DefaultDigestLength, default(ByteSpan)); /// public static IBlake2Incremental CreateIncrementalHasher(int digestLength) => CreateIncrementalHasher(digestLength, default(ByteSpan)); /// - public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(Blake2bContext.HashBytes, key); + public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(DefaultDigestLength, key); - /// Create and initialize an incremental Blake2b hash computation. + /// Create and initialize an incremental BLAKE2b hash computation. /// If you will recieve the input in segments rather than all at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to 64. /// 0 to 64 bytes of input for initializing a keyed hash. @@ -70,6 +111,18 @@ public static IBlake2Incremental CreateIncrementalHasher(int digestLength, ByteS /// public static byte[] ComputeHash(int digestLength, byte[] key, byte[] input) => ComputeHash(digestLength, key.AsByteSpan(), input.AsByteSpan()); + /// + public static void ComputeAndWriteHash(byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + /// public static IBlake2Incremental CreateIncrementalHasher(byte[] key) => CreateIncrementalHasher(key.AsByteSpan()); @@ -79,18 +132,18 @@ public static IBlake2Incremental CreateIncrementalHasher(int digestLength, ByteS #if !NETSTANDARD1_0 /// - public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(Blake2bContext.HashBytes); + public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(DefaultDigestLength); - /// Creates and initializes a instance that implements Blake2b hashing. + /// Creates and initializes a instance that implements BLAKE2b hashing. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to 64. /// A instance. public static HashAlgorithm CreateHashAlgorithm(int digestLength) => new Blake2HMAC(Blake2Algorithm.Blake2b, digestLength, default); /// - public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(Blake2bContext.HashBytes, key); + public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(DefaultDigestLength, key); - /// Creates and initializes an instance that implements Blake2b keyed hashing. + /// Creates and initializes an instance that implements BLAKE2b keyed hashing. Uses BLAKE2's built-in support for keyed hashing rather than the normal 2-pass approach. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to 64. /// 0 to 64 bytes of input for initializing the keyed hash. diff --git a/src/Blake2Fast/Blake2bContext.cs b/src/Blake2Fast/Blake2bContext.cs index b691f2e..9195541 100644 --- a/src/Blake2Fast/Blake2bContext.cs +++ b/src/Blake2Fast/Blake2bContext.cs @@ -171,10 +171,6 @@ public void Update(ByteSpan input) } } -#if !IMPLICIT_BYTESPAN - public void Update(byte[] input) => Update(input.AsByteSpan()); -#endif - private void finish(WriteableByteSpan hash) { if (this.f[0] != 0) @@ -212,10 +208,13 @@ public byte[] Finish() return hash; } -#if FAST_SPAN public bool TryFinish(WriteableByteSpan output, out int bytesWritten) { +#if FAST_SPAN if (output.Length < outlen) +#else + if (output.Count < outlen) +#endif { bytesWritten = 0; return false; @@ -225,6 +224,11 @@ public bool TryFinish(WriteableByteSpan output, out int bytesWritten) bytesWritten = (int)outlen; return true; } + +#if !IMPLICIT_BYTESPAN + public void Update(byte[] input) => Update(input.AsByteSpan()); + + public bool TryFinish(byte[] output, out int bytesWritten) => TryFinish(output.AsByteSpan(), out bytesWritten); #endif } } diff --git a/src/Blake2Fast/Blake2s.cs b/src/Blake2Fast/Blake2s.cs index a38b3c6..d7991ca 100644 --- a/src/Blake2Fast/Blake2s.cs +++ b/src/Blake2Fast/Blake2s.cs @@ -1,28 +1,35 @@ -#if !NETSTANDARD1_0 +using System; + +#if !NETSTANDARD1_0 using System.Security.Cryptography; #endif #if FAST_SPAN using ByteSpan = System.ReadOnlySpan; +using WriteableByteSpan = System.Span; #else using ByteSpan = System.ArraySegment; +using WriteableByteSpan = System.ArraySegment; #endif namespace SauceControl.Blake2Fast { - /// Static helper methods for Blake2s hashing. + /// Static helper methods for BLAKE2s hashing. public static class Blake2s { + /// The default hash digest length in bytes. For BLAKE2s, this value is 32. + public const int DefaultDigestLength = Blake2sContext.HashBytes; + /// - public static byte[] ComputeHash(ByteSpan input) => ComputeHash(Blake2sContext.HashBytes, default, input); + public static byte[] ComputeHash(ByteSpan input) => ComputeHash(DefaultDigestLength, default, input); /// public static byte[] ComputeHash(int digestLength, ByteSpan input) => ComputeHash(digestLength, default, input); /// - public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(Blake2sContext.HashBytes, key, input); + public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(DefaultDigestLength, key, input); - /// Perform an all-at-once Blake2s hash computation. + /// Perform an all-at-once BLAKE2s hash computation. /// If you have all the input available at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to 32. /// 0 to 32 bytes of input for initializing a keyed hash. @@ -36,16 +43,50 @@ public static byte[] ComputeHash(int digestLength, ByteSpan key, ByteSpan input) return ctx.Finish(); } + /// + public static void ComputeAndWriteHash(ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, default, input, output); + + /// + public static void ComputeAndWriteHash(int digestLength, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(digestLength, default, input, output); + + /// Perform an all-at-once BLAKE2s hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// 0 to 32 bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least (32) /> bytes. + public static void ComputeAndWriteHash(ByteSpan key, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, key, input, output); + + /// Perform an all-at-once BLAKE2s hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// The hash digest length in bytes. Valid values are 1 to 32. + /// 0 to 32 bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least bytes. + public static void ComputeAndWriteHash(int digestLength, ByteSpan key, ByteSpan input, WriteableByteSpan output) + { +#if FAST_SPAN + if (output.Length < digestLength) +#else + if (output.Count < digestLength) +#endif + throw new ArgumentException($"Output buffer must have a capacity of at least {digestLength} bytes.", nameof(output)); + + var ctx = default(Blake2sContext); + ctx.Init(digestLength, key); + ctx.Update(input); + ctx.TryFinish(output, out int _); + } + /// - public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(Blake2sContext.HashBytes, default(ByteSpan)); + public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(DefaultDigestLength, default(ByteSpan)); /// public static IBlake2Incremental CreateIncrementalHasher(int digestLength) => CreateIncrementalHasher(digestLength, default(ByteSpan)); /// - public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(Blake2sContext.HashBytes, key); + public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(DefaultDigestLength, key); - /// Create and initialize an incremental Blake2s hash computation. + /// Create and initialize an incremental BLAKE2s hash computation. /// If you will recieve the input in segments rather than all at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to 32. /// 0 to 32 bytes of input for initializing a keyed hash. @@ -70,6 +111,18 @@ public static IBlake2Incremental CreateIncrementalHasher(int digestLength, ByteS /// public static byte[] ComputeHash(int digestLength, byte[] key, byte[] input) => ComputeHash(digestLength, key.AsByteSpan(), input.AsByteSpan()); + /// + public static void ComputeAndWriteHash(byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + /// public static IBlake2Incremental CreateIncrementalHasher(byte[] key) => CreateIncrementalHasher(key.AsByteSpan()); @@ -79,18 +132,18 @@ public static IBlake2Incremental CreateIncrementalHasher(int digestLength, ByteS #if !NETSTANDARD1_0 /// - public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(Blake2sContext.HashBytes); + public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(DefaultDigestLength); - /// Creates and initializes a instance that implements Blake2s hashing. + /// Creates and initializes a instance that implements BLAKE2s hashing. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to 32. /// A instance. public static HashAlgorithm CreateHashAlgorithm(int digestLength) => new Blake2HMAC(Blake2Algorithm.Blake2s, digestLength, default); /// - public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(Blake2sContext.HashBytes, key); + public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(DefaultDigestLength, key); - /// Creates and initializes an instance that implements Blake2s keyed hashing. + /// Creates and initializes an instance that implements BLAKE2s keyed hashing. Uses BLAKE2's built-in support for keyed hashing rather than the normal 2-pass approach. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to 32. /// 0 to 32 bytes of input for initializing the keyed hash. diff --git a/src/Blake2Fast/Blake2sContext.cs b/src/Blake2Fast/Blake2sContext.cs index b5e2de7..3d3581a 100644 --- a/src/Blake2Fast/Blake2sContext.cs +++ b/src/Blake2Fast/Blake2sContext.cs @@ -171,10 +171,6 @@ public void Update(ByteSpan input) } } -#if !IMPLICIT_BYTESPAN - public void Update(byte[] input) => Update(input.AsByteSpan()); -#endif - private void finish(WriteableByteSpan hash) { if (this.f[0] != 0) @@ -212,10 +208,13 @@ public byte[] Finish() return hash; } -#if FAST_SPAN public bool TryFinish(WriteableByteSpan output, out int bytesWritten) { +#if FAST_SPAN if (output.Length < outlen) +#else + if (output.Count < outlen) +#endif { bytesWritten = 0; return false; @@ -225,6 +224,11 @@ public bool TryFinish(WriteableByteSpan output, out int bytesWritten) bytesWritten = (int)outlen; return true; } + +#if !IMPLICIT_BYTESPAN + public void Update(byte[] input) => Update(input.AsByteSpan()); + + public bool TryFinish(byte[] output, out int bytesWritten) => TryFinish(output.AsByteSpan(), out bytesWritten); #endif } } diff --git a/src/Blake2Fast/Common.cs b/src/Blake2Fast/Common.cs index 4c9947f..f1630d1 100644 --- a/src/Blake2Fast/Common.cs +++ b/src/Blake2Fast/Common.cs @@ -3,19 +3,15 @@ using WriteableByteSpan = System.Span; #else using ByteSpan = System.ArraySegment; +using WriteableByteSpan = System.ArraySegment; #endif namespace SauceControl.Blake2Fast { - /// Defines an incremental Blake2 hashing operation. + /// Defines an incremental BLAKE2 hashing operation. /// Allows the hash to be computed as portions of the message become available, rather than all at once. public interface IBlake2Incremental { -#if !IMPLICIT_BYTESPAN - /// - void Update(byte[] input); -#endif - /// Update the hash state with the message bytes contained in . /// The message bytes to add to the hash state. void Update(ByteSpan input); @@ -24,12 +20,18 @@ public interface IBlake2Incremental /// The computed hash digest. byte[] Finish(); -#if FAST_SPAN /// Finalize the hash, and copy the computed digest to . /// The buffer into which the hash digest should be written. /// On return, contains the number of bytes written to . /// True if the buffer was large enough to hold the digest, otherwise False. bool TryFinish(WriteableByteSpan output, out int bytesWritten); + +#if !IMPLICIT_BYTESPAN + /// + void Update(byte[] input); + + /// + bool TryFinish(byte[] output, out int bytesWritten); #endif } diff --git a/src/Blake2Fast/_Blake2Api.ttinclude b/src/Blake2Fast/_Blake2Api.ttinclude index ac1e434..7ab5e0d 100644 --- a/src/Blake2Fast/_Blake2Api.ttinclude +++ b/src/Blake2Fast/_Blake2Api.ttinclude @@ -1,28 +1,35 @@ -#if !NETSTANDARD1_0 +using System; + +#if !NETSTANDARD1_0 using System.Security.Cryptography; #endif #if FAST_SPAN using ByteSpan = System.ReadOnlySpan; +using WriteableByteSpan = System.Span; #else using ByteSpan = System.ArraySegment; +using WriteableByteSpan = System.ArraySegment; #endif namespace SauceControl.Blake2Fast { - /// Static helper methods for Blake2<#= alg.suffix #> hashing. + /// Static helper methods for BLAKE2<#= alg.suffix #> hashing. public static class Blake2<#= alg.suffix #> { + /// The default hash digest length in bytes. For BLAKE2<#= alg.suffix #>, this value is <#= alg.bits #>. + public const int DefaultDigestLength = Blake2<#= alg.suffix #>Context.HashBytes; + /// - public static byte[] ComputeHash(ByteSpan input) => ComputeHash(Blake2<#= alg.suffix #>Context.HashBytes, default, input); + public static byte[] ComputeHash(ByteSpan input) => ComputeHash(DefaultDigestLength, default, input); /// public static byte[] ComputeHash(int digestLength, ByteSpan input) => ComputeHash(digestLength, default, input); /// - public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(Blake2<#= alg.suffix #>Context.HashBytes, key, input); + public static byte[] ComputeHash(ByteSpan key, ByteSpan input) => ComputeHash(DefaultDigestLength, key, input); - /// Perform an all-at-once Blake2<#= alg.suffix #> hash computation. + /// Perform an all-at-once BLAKE2<#= alg.suffix #> hash computation. /// If you have all the input available at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to <#= alg.bits #>. /// 0 to <#= alg.bits #> bytes of input for initializing a keyed hash. @@ -36,16 +43,50 @@ namespace SauceControl.Blake2Fast return ctx.Finish(); } + /// + public static void ComputeAndWriteHash(ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, default, input, output); + + /// + public static void ComputeAndWriteHash(int digestLength, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(digestLength, default, input, output); + + /// Perform an all-at-once BLAKE2<#= alg.suffix #> hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// 0 to <#= alg.bits #> bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least (<#= alg.bits #>) /> bytes. + public static void ComputeAndWriteHash(ByteSpan key, ByteSpan input, WriteableByteSpan output) => ComputeAndWriteHash(DefaultDigestLength, key, input, output); + + /// Perform an all-at-once BLAKE2<#= alg.suffix #> hash computation and write the hash digest to . + /// If you have all the input available at once, this is the most efficient way to calculate the hash. + /// The hash digest length in bytes. Valid values are 1 to <#= alg.bits #>. + /// 0 to <#= alg.bits #> bytes of input for initializing a keyed hash. + /// The message bytes to hash. + /// Destination buffer into which the hash digest is written. The buffer must have a capacity of at least bytes. + public static void ComputeAndWriteHash(int digestLength, ByteSpan key, ByteSpan input, WriteableByteSpan output) + { +#if FAST_SPAN + if (output.Length < digestLength) +#else + if (output.Count < digestLength) +#endif + throw new ArgumentException($"Output buffer must have a capacity of at least {digestLength} bytes.", nameof(output)); + + var ctx = default(Blake2<#= alg.suffix #>Context); + ctx.Init(digestLength, key); + ctx.Update(input); + ctx.TryFinish(output, out int _); + } + /// - public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(Blake2<#= alg.suffix #>Context.HashBytes, default(ByteSpan)); + public static IBlake2Incremental CreateIncrementalHasher() => CreateIncrementalHasher(DefaultDigestLength, default(ByteSpan)); /// public static IBlake2Incremental CreateIncrementalHasher(int digestLength) => CreateIncrementalHasher(digestLength, default(ByteSpan)); /// - public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(Blake2<#= alg.suffix #>Context.HashBytes, key); + public static IBlake2Incremental CreateIncrementalHasher(ByteSpan key) => CreateIncrementalHasher(DefaultDigestLength, key); - /// Create and initialize an incremental Blake2<#= alg.suffix #> hash computation. + /// Create and initialize an incremental BLAKE2<#= alg.suffix #> hash computation. /// If you will recieve the input in segments rather than all at once, this is the most efficient way to calculate the hash. /// The hash digest length in bytes. Valid values are 1 to <#= alg.bits #>. /// 0 to <#= alg.bits #> bytes of input for initializing a keyed hash. @@ -70,6 +111,18 @@ namespace SauceControl.Blake2Fast /// public static byte[] ComputeHash(int digestLength, byte[] key, byte[] input) => ComputeHash(digestLength, key.AsByteSpan(), input.AsByteSpan()); + /// + public static void ComputeAndWriteHash(byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, default, input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(DefaultDigestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + + /// + public static void ComputeAndWriteHash(int digestLength, byte[] key, byte[] input, byte[] output) => ComputeAndWriteHash(digestLength, key.AsByteSpan(), input.AsByteSpan(), output.AsByteSpan()); + /// public static IBlake2Incremental CreateIncrementalHasher(byte[] key) => CreateIncrementalHasher(key.AsByteSpan()); @@ -79,18 +132,18 @@ namespace SauceControl.Blake2Fast #if !NETSTANDARD1_0 /// - public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(Blake2<#= alg.suffix #>Context.HashBytes); + public static HashAlgorithm CreateHashAlgorithm() => CreateHashAlgorithm(DefaultDigestLength); - /// Creates and initializes a instance that implements Blake2<#= alg.suffix #> hashing. + /// Creates and initializes a instance that implements BLAKE2<#= alg.suffix #> hashing. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to <#= alg.bits #>. /// A instance. public static HashAlgorithm CreateHashAlgorithm(int digestLength) => new Blake2HMAC(Blake2Algorithm.Blake2<#= alg.suffix #>, digestLength, default); /// - public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(Blake2<#= alg.suffix #>Context.HashBytes, key); + public static HMAC CreateHMAC(ByteSpan key) => CreateHMAC(DefaultDigestLength, key); - /// Creates and initializes an instance that implements Blake2<#= alg.suffix #> keyed hashing. + /// Creates and initializes an instance that implements BLAKE2<#= alg.suffix #> keyed hashing. Uses BLAKE2's built-in support for keyed hashing rather than the normal 2-pass approach. /// Use this only if you require an implementation of . It is less efficient than the direct methods. /// The hash digest length in bytes. Valid values are 1 to <#= alg.bits #>. /// 0 to <#= alg.bits #> bytes of input for initializing the keyed hash. diff --git a/src/Blake2Fast/_Blake2Main.ttinclude b/src/Blake2Fast/_Blake2Main.ttinclude index 16dcb5f..7252e54 100644 --- a/src/Blake2Fast/_Blake2Main.ttinclude +++ b/src/Blake2Fast/_Blake2Main.ttinclude @@ -194,10 +194,6 @@ namespace SauceControl.Blake2Fast } } -#if !IMPLICIT_BYTESPAN - public void Update(byte[] input) => Update(input.AsByteSpan()); -#endif - private void finish(WriteableByteSpan hash) { if (this.f[0] != 0) @@ -235,10 +231,13 @@ namespace SauceControl.Blake2Fast return hash; } -#if FAST_SPAN public bool TryFinish(WriteableByteSpan output, out int bytesWritten) { +#if FAST_SPAN if (output.Length < outlen) +#else + if (output.Count < outlen) +#endif { bytesWritten = 0; return false; @@ -248,6 +247,11 @@ namespace SauceControl.Blake2Fast bytesWritten = (int)outlen; return true; } + +#if !IMPLICIT_BYTESPAN + public void Update(byte[] input) => Update(input.AsByteSpan()); + + public bool TryFinish(byte[] output, out int bytesWritten) => TryFinish(output.AsByteSpan(), out bytesWritten); #endif } } diff --git a/tests/Blake2.Test/Blake2.Test.csproj b/tests/Blake2.Test/Blake2.Test.csproj index 51f40ec..48624cf 100644 --- a/tests/Blake2.Test/Blake2.Test.csproj +++ b/tests/Blake2.Test/Blake2.Test.csproj @@ -2,11 +2,13 @@ netcoreapp2.1;netcoreapp2.0;netcoreapp1.0;net46 + latest false - - $(DefineConstants);ICRYPTOTRANSFORM + + $(DefineConstants);ICRYPTOTRANSFORM + $(DefineConstants);FAST_SPAN diff --git a/tests/Blake2.Test/RfcSelfTest.cs b/tests/Blake2.Test/RfcSelfTest.cs index 7260a4f..391c61f 100644 --- a/tests/Blake2.Test/RfcSelfTest.cs +++ b/tests/Blake2.Test/RfcSelfTest.cs @@ -4,6 +4,11 @@ using Xunit; using SauceControl.Blake2Fast; +internal static class ArrayExtension +{ + public static ArraySegment Slice(this T[] a, int start, int count) => new ArraySegment(a, start, count); +} + public class RfcSelfTest { private static readonly byte[] blake2bCheck = new byte[] { @@ -71,6 +76,56 @@ private static bool blake2sSelfTest() return inc.Finish().SequenceEqual(blake2sCheck); } + private static bool blake2bNoAllocSelfTest() + { +#if FAST_SPAN + Span buff = stackalloc byte[Blake2b.DefaultDigestLength]; +#else + var buff = new byte[Blake2b.DefaultDigestLength]; +#endif + var inc = Blake2b.CreateIncrementalHasher(blake2bCheck.Length); + + foreach (int diglen in new[] { 20, 32, 48, 64 }) + foreach (int msglen in new[] { 0, 3, 128, 129, 255, 1024 }) + { + var msg = getTestSequence(msglen); + var key = getTestSequence(diglen); + + Blake2b.ComputeAndWriteHash(diglen, msg, buff); + inc.Update(buff.Slice(0, diglen)); + + Blake2b.ComputeAndWriteHash(diglen, key, msg, buff); + inc.Update(buff.Slice(0, diglen)); + } + + return inc.TryFinish(buff, out int len) && buff.Slice(0, len).SequenceEqual(blake2bCheck); + } + + private static bool blake2sNoAllocSelfTest() + { +#if FAST_SPAN + Span buff = stackalloc byte[Blake2b.DefaultDigestLength]; +#else + var buff = new byte[Blake2b.DefaultDigestLength]; +#endif + var inc = Blake2s.CreateIncrementalHasher(blake2sCheck.Length); + + foreach (int diglen in new[] { 16, 20, 28, 32 }) + foreach (int msglen in new[] { 0, 3, 64, 65, 255, 1024 }) + { + var msg = getTestSequence(msglen); + var key = getTestSequence(diglen); + + Blake2s.ComputeAndWriteHash(diglen, msg, buff); + inc.Update(buff.Slice(0, diglen)); + + Blake2s.ComputeAndWriteHash(diglen, key, msg, buff); + inc.Update(buff.Slice(0, diglen)); + } + + return inc.TryFinish(buff, out int len) && buff.Slice(0, len).SequenceEqual(blake2sCheck); + } + private static bool blake2bHmacSelfTest() { #if ICRYPTOTRANSFORM @@ -157,6 +212,18 @@ public void TestBlake2s() Assert.True(blake2sSelfTest()); } + [Fact] + public void TestBlake2bNoAlloc() + { + Assert.True(blake2bNoAllocSelfTest()); + } + + [Fact] + public void TestBlake2sNoAlloc() + { + Assert.True(blake2sNoAllocSelfTest()); + } + [Fact] public void TestBlake2bHMAC() {