Blake2B support.

This commit is contained in:
Pieter-Jan Briers
2022-04-09 15:13:14 +02:00
parent d19f9c9e5b
commit 10f6adb297
3 changed files with 314 additions and 4 deletions

View File

@@ -0,0 +1,107 @@
using System;
using System.Text;
using NUnit.Framework;
namespace SpaceWizards.Sodium.Tests;
[TestFixture]
[TestOf(typeof(CryptoGenericHashBlake2B))]
[Parallelizable(ParallelScope.All)]
public sealed class GenericHashBlake2BTest
{
[Test]
[TestCase("foobar", null, "93a0e84a8cdd4166267dbe1263e937f08087723ac24e7dcc35b3d5941775ef47")]
[TestCase("foobar", null,
"8df31f60d6aeabd01b7dc83f277d0e24cbe104f7290ff89077a7eb58646068edfe1a83022866c46f65fb91612e516e0ecfa5cb25fc16b37d2c8d73732fe74cb2")]
[TestCase("foobar", "baz", "5f49889d5da33b8a04b242f19986193f0401d8fe087040ed79ae955119638a45")]
public void TestHash(string inputStr, string? keyStr, string expectedHex)
{
var input = Encoding.UTF8.GetBytes(inputStr);
var key = keyStr == null ? null : Encoding.UTF8.GetBytes(keyStr);
var expected = Convert.FromHexString(expectedHex);
var output = CryptoGenericHashBlake2B.Hash(expected.Length, input, key);
Assert.That(output, Is.EquivalentTo(expected));
}
[Test]
// @formatter:off
[TestCase("foobar", null, null, null, "93a0e84a8cdd4166267dbe1263e937f08087723ac24e7dcc35b3d5941775ef47")]
[TestCase("foobar", null, null, null, "8df31f60d6aeabd01b7dc83f277d0e24cbe104f7290ff89077a7eb58646068edfe1a83022866c46f65fb91612e516e0ecfa5cb25fc16b37d2c8d73732fe74cb2")]
[TestCase("foobar", "baz", null, null, "5f49889d5da33b8a04b242f19986193f0401d8fe087040ed79ae955119638a45")]
[TestCase("foobar", "baz", "AAAABBBBCCCCDDDD", null, "d4898e5ec36873d27d87ab00a464be00a7c8be03b9b5c01defd6e9d1e7150ebb")]
[TestCase("foobar", "baz", "AAAABBBBCCCCDDDD", "DDDDCCCCBBBBAAAA", "ccfa20580f3162c9312aa9ba39e88bc1e6857ebb5dcad2726d3835207cf8d735")]
[TestCase("foobar", "baz", null, "DDDDCCCCBBBBAAAA", "75d3b6c777060f178299a5fb16846013fb97354305598636117493a57b117282")]
[TestCase("foobar", null, null, "DDDDCCCCBBBBAAAA", "f5f01f9992ce6db03a37b9485d17bdc8f26154d6bc70e0524124b9bbeafd0269")]
// @formatter:on
public void TestHashSaltPersonal(
string inputStr,
string? keyStr,
string? saltStr,
string? personalStr,
string expectedHex)
{
var input = Encoding.UTF8.GetBytes(inputStr);
var key = keyStr == null ? null : Encoding.UTF8.GetBytes(keyStr);
var salt = saltStr == null ? null : Encoding.UTF8.GetBytes(saltStr);
var personal = personalStr == null ? null : Encoding.UTF8.GetBytes(personalStr);
var expected = Convert.FromHexString(expectedHex);
var output = CryptoGenericHashBlake2B.HashSaltPersonal(expected.Length, input, key, salt, personal);
Assert.That(output, Is.EquivalentTo(expected));
}
[Test]
[TestCase("foobar", null, "93a0e84a8cdd4166267dbe1263e937f08087723ac24e7dcc35b3d5941775ef47")]
[TestCase("foobar", null,
"8df31f60d6aeabd01b7dc83f277d0e24cbe104f7290ff89077a7eb58646068edfe1a83022866c46f65fb91612e516e0ecfa5cb25fc16b37d2c8d73732fe74cb2")]
[TestCase("foobar", "baz", "5f49889d5da33b8a04b242f19986193f0401d8fe087040ed79ae955119638a45")]
public void TestHashIncremental(string inputStr, string? keyStr, string expectedHex)
{
var input = Encoding.UTF8.GetBytes(inputStr);
var key = keyStr == null ? null : Encoding.UTF8.GetBytes(keyStr);
var expected = Convert.FromHexString(expectedHex);
var output = new byte[expected.Length];
CryptoGenericHashBlake2B.State state;
CryptoGenericHashBlake2B.Init(ref state, key, expected.Length);
CryptoGenericHashBlake2B.Update(ref state, input);
CryptoGenericHashBlake2B.Final(ref state, output);
Assert.That(output, Is.EquivalentTo(expected));
}
[Test]
// @formatter:off
[TestCase("foobar", null, null, null, "93a0e84a8cdd4166267dbe1263e937f08087723ac24e7dcc35b3d5941775ef47")]
[TestCase("foobar", null, null, null, "8df31f60d6aeabd01b7dc83f277d0e24cbe104f7290ff89077a7eb58646068edfe1a83022866c46f65fb91612e516e0ecfa5cb25fc16b37d2c8d73732fe74cb2")]
[TestCase("foobar", "baz", null, null, "5f49889d5da33b8a04b242f19986193f0401d8fe087040ed79ae955119638a45")]
[TestCase("foobar", "baz", "AAAABBBBCCCCDDDD", null, "d4898e5ec36873d27d87ab00a464be00a7c8be03b9b5c01defd6e9d1e7150ebb")]
[TestCase("foobar", "baz", "AAAABBBBCCCCDDDD", "DDDDCCCCBBBBAAAA", "ccfa20580f3162c9312aa9ba39e88bc1e6857ebb5dcad2726d3835207cf8d735")]
[TestCase("foobar", "baz", null, "DDDDCCCCBBBBAAAA", "75d3b6c777060f178299a5fb16846013fb97354305598636117493a57b117282")]
[TestCase("foobar", null, null, "DDDDCCCCBBBBAAAA", "f5f01f9992ce6db03a37b9485d17bdc8f26154d6bc70e0524124b9bbeafd0269")]
// @formatter:on
public void TestHashSaltPersonalIncremental(
string inputStr,
string? keyStr,
string? saltStr,
string? personalStr,
string expectedHex)
{
var input = Encoding.UTF8.GetBytes(inputStr);
var key = keyStr == null ? null : Encoding.UTF8.GetBytes(keyStr);
var salt = saltStr == null ? null : Encoding.UTF8.GetBytes(saltStr);
var personal = personalStr == null ? null : Encoding.UTF8.GetBytes(personalStr);
var expected = Convert.FromHexString(expectedHex);
var output = new byte[expected.Length];
CryptoGenericHashBlake2B.State state;
CryptoGenericHashBlake2B.InitSaltPersonal(ref state, key, expected.Length, salt, personal);
CryptoGenericHashBlake2B.Update(ref state, input);
CryptoGenericHashBlake2B.Final(ref state, output);
Assert.That(output, Is.EquivalentTo(expected));
}
}

View File

@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -18,7 +18,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SpaceWizards.Sodium.Interop\SpaceWizards.Sodium.Interop.csproj"/>
<ProjectReference Include="..\SpaceWizards.Sodium.Interop\SpaceWizards.Sodium.Interop.csproj" />
<ProjectReference Include="..\SpaceWizards.Sodium\SpaceWizards.Sodium.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,202 @@
using SpaceWizards.Sodium.Interop;
namespace SpaceWizards.Sodium;
using static Interop.Libsodium;
/// <summary>
/// Wrappers around the <c>crypto_generichash_blake2b_</c> APIs.
/// </summary>
public static class CryptoGenericHashBlake2B
{
static CryptoGenericHashBlake2B()
{
SodiumCore.EnsureInit();
}
public const int BytesMin = (int)crypto_generichash_blake2b_BYTES_MIN;
public const int BytesMax = (int)crypto_generichash_blake2b_BYTES_MAX;
public const int Bytes = (int)crypto_generichash_blake2b_BYTES;
public const int KeyBytesMin = (int)crypto_generichash_blake2b_KEYBYTES_MIN;
public const int KeyBytesMax = (int)crypto_generichash_blake2b_KEYBYTES_MAX;
public const int KeyBytes = (int)crypto_generichash_blake2b_BYTES;
public const int SaltBytes = (int)crypto_generichash_blake2b_SALTBYTES;
public const int PersonalBytes = (int)crypto_generichash_blake2b_PERSONALBYTES;
public static unsafe void Keygen(Span<byte> key)
{
if (key.Length != KeyBytes)
throw new ArgumentException($"Key must be {nameof(KeyBytes)} bytes");
fixed (byte* k = key)
{
crypto_generichash_blake2b_keygen(k);
}
}
public static byte[] Keygen()
{
var key = new byte[KeyBytes];
Keygen(key);
return key;
}
public static byte[] Hash(
int outputLength,
ReadOnlySpan<byte> input,
ReadOnlySpan<byte> key)
{
var output = new byte[outputLength];
Hash(output, input, key);
return output;
}
public static unsafe bool Hash(Span<byte> output, ReadOnlySpan<byte> input, ReadOnlySpan<byte> key)
{
if (key.Length > KeyBytesMax)
throw new ArgumentException("Key too large");
if (output.Length is < BytesMin or > BytesMax)
throw new ArgumentException("Output is invalid size");
fixed (byte* i = input)
fixed (byte* o = output)
fixed (byte* k = key)
{
var ret = crypto_generichash_blake2b(
o, (nuint)output.Length,
i, (ulong)input.Length,
k, (nuint)key.Length);
return ret == 0;
}
}
public static byte[] HashSaltPersonal(
int outputLength,
ReadOnlySpan<byte> input,
ReadOnlySpan<byte> key,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> personal)
{
var output = new byte[outputLength];
HashSaltPersonal(output, input, key, salt, personal);
return output;
}
public static unsafe bool HashSaltPersonal(
Span<byte> output,
ReadOnlySpan<byte> input,
ReadOnlySpan<byte> key,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> personal)
{
if (key.Length > KeyBytesMax)
throw new ArgumentException("Key too large");
if (output.Length is < BytesMin or > BytesMax)
throw new ArgumentException("Output is invalid size");
if (salt.Length != SaltBytes && salt.Length != 0)
throw new ArgumentException($"Salt must be {nameof(SaltBytes)} bytes or empty");
if (personal.Length != PersonalBytes && personal.Length != 0)
throw new ArgumentException($"Personalization must be {nameof(PersonalBytes)} bytes or empty");
fixed (byte* i = input)
fixed (byte* o = output)
fixed (byte* k = key)
fixed (byte* s = salt)
fixed (byte* p = personal)
{
var ret = crypto_generichash_blake2b_salt_personal(
o, (nuint)output.Length,
i, (ulong)input.Length,
k, (nuint)key.Length,
s,
p);
return ret == 0;
}
}
public static unsafe bool Init(ref State state, ReadOnlySpan<byte> key, int outputLength)
{
if (key.Length > KeyBytesMax)
throw new ArgumentException("Key too large");
if (outputLength is < BytesMin or > BytesMax)
throw new ArgumentException("Output is invalid size");
fixed (crypto_generichash_blake2b_state* s = &state.Data)
fixed (byte* k = key)
{
var ret = crypto_generichash_blake2b_init(s, k, (nuint)key.Length, (nuint)outputLength);
return ret == 0;
}
}
public static unsafe bool InitSaltPersonal(
ref State state,
ReadOnlySpan<byte> key,
int outputLength,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> personal)
{
if (key.Length > KeyBytesMax)
throw new ArgumentException("Key too large");
if (outputLength is < BytesMin or > BytesMax)
throw new ArgumentException("Output is invalid size");
fixed (crypto_generichash_blake2b_state* s = &state.Data)
fixed (byte* k = key)
fixed (byte* saltPtr = salt)
fixed (byte* p = personal)
{
var ret = crypto_generichash_blake2b_init_salt_personal(
s,
k, (nuint)key.Length,
(nuint)outputLength,
saltPtr,
p);
return ret == 0;
}
}
public static unsafe bool Update(ref State state, ReadOnlySpan<byte> input)
{
fixed (crypto_generichash_blake2b_state* s = &state.Data)
fixed (byte* i = input)
{
var ret = crypto_generichash_blake2b_update(s, i, (nuint)input.Length);
return ret == 0;
}
}
public static unsafe bool Final(ref State state, Span<byte> output)
{
if (output.Length is < BytesMin or > BytesMax)
throw new ArgumentException("Output is invalid size");
fixed (crypto_generichash_blake2b_state* s = &state.Data)
fixed (byte* o = output)
{
var ret = crypto_generichash_blake2b_final(s, o, (nuint)output.Length);
return ret == 0;
}
}
public struct State
{
public crypto_generichash_blake2b_state Data;
}
}