Blake2bDigest
Implementation of the cryptographic hash function Blake2b.
BLAKE2b is optimized for 64-bit platforms and produces digests of any size
between 1 and 64 bytes.
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
using System;
using System.Runtime.CompilerServices;
namespace Org.BouncyCastle.Crypto.Digests
{
public sealed class Blake2bDigest : IDigest
{
private static readonly ulong[] IV = new ulong[8] {
7640891576956012808,
13503953896175478587,
4354685564936845355,
11912009170470909681,
5840696475078001361,
11170449401992604703,
2270897969802886507,
6620516959819538809
};
private static readonly byte[] Sigma = new byte[192] {
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
14,
10,
4,
8,
9,
15,
13,
6,
1,
12,
0,
2,
11,
7,
5,
3,
11,
8,
12,
0,
5,
2,
15,
13,
10,
14,
3,
6,
7,
1,
9,
4,
7,
9,
3,
1,
13,
12,
11,
14,
2,
6,
5,
10,
4,
0,
15,
8,
9,
0,
5,
7,
2,
4,
10,
15,
14,
1,
11,
12,
6,
8,
3,
13,
2,
12,
6,
10,
0,
11,
8,
3,
4,
13,
7,
5,
15,
14,
1,
9,
12,
5,
1,
15,
14,
13,
4,
10,
0,
7,
6,
3,
9,
2,
8,
11,
13,
11,
7,
14,
12,
1,
3,
9,
5,
0,
15,
4,
8,
6,
2,
10,
6,
15,
14,
9,
11,
3,
0,
8,
12,
2,
13,
7,
1,
4,
10,
5,
10,
2,
8,
4,
7,
6,
1,
5,
15,
11,
9,
14,
3,
12,
13,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
14,
10,
4,
8,
9,
15,
13,
6,
1,
12,
0,
2,
11,
7,
5,
3
};
private const int ROUNDS = 12;
private const int BLOCK_LENGTH_BYTES = 128;
private readonly ulong[] chainValue = new ulong[8];
private readonly byte[] buffer = new byte[128];
private int digestLength = 64;
private byte[] m_salt;
private byte[] m_personalization;
private byte[] m_key;
private int bufferPos;
private ulong t0;
private ulong t1;
private ulong f0;
public string AlgorithmName => "BLAKE2b";
public Blake2bDigest()
: this(512)
{
}
public Blake2bDigest(Blake2bDigest digest)
{
Array.Copy(digest.chainValue, 0, chainValue, 0, 8);
Array.Copy(digest.buffer, 0, buffer, 0, 128);
bufferPos = digest.bufferPos;
m_key = Arrays.Clone(digest.m_key);
digestLength = digest.digestLength;
m_personalization = Arrays.Clone(digest.m_personalization);
m_salt = Arrays.Clone(digest.m_salt);
t0 = digest.t0;
t1 = digest.t1;
f0 = digest.f0;
}
public Blake2bDigest(int digestSize)
{
if (digestSize < 8 || digestSize > 512 || digestSize % 8 != 0)
throw new ArgumentException("BLAKE2b digest bit length must be a multiple of 8 and not greater than 512");
digestLength = digestSize / 8;
Init();
}
public Blake2bDigest(byte[] key)
{
digestLength = 64;
if (key != null && key.Length != 0) {
if (key.Length > 64)
throw new ArgumentException("Keys > 64 are not supported", "key");
m_key = (byte[])key.Clone();
}
Init();
}
public Blake2bDigest(byte[] key, int digestLength, byte[] salt, byte[] personalization)
{
if (digestLength < 1 || digestLength > 64)
throw new ArgumentException("Invalid digest length (required: 1 - 64)");
this.digestLength = digestLength;
if (key != null && key.Length != 0) {
if (key.Length > 64)
throw new ArgumentException("Keys > 64 are not supported", "key");
m_key = (byte[])key.Clone();
}
if (salt != null) {
if (salt.Length != 16)
throw new ArgumentException("salt length must be exactly 16 bytes", "salt");
m_salt = (byte[])salt.Clone();
}
if (personalization != null) {
if (personalization.Length != 16)
throw new ArgumentException("personalization length must be exactly 16 bytes", "personalization");
m_personalization = (byte[])personalization.Clone();
}
Init();
}
private void Init()
{
int num = 0;
if (m_key != null) {
num = m_key.Length;
Array.Copy(m_key, 0, buffer, 0, num);
bufferPos = 128;
}
chainValue[0] = (ulong)((long)IV[0] ^ (long)(digestLength | (num << 8) | 16842752));
chainValue[1] = IV[1];
chainValue[2] = IV[2];
chainValue[3] = IV[3];
chainValue[4] = IV[4];
chainValue[5] = IV[5];
if (m_salt != null) {
chainValue[4] ^= Pack.LE_To_UInt64(m_salt, 0);
chainValue[5] ^= Pack.LE_To_UInt64(m_salt, 8);
}
chainValue[6] = IV[6];
chainValue[7] = IV[7];
if (m_personalization != null) {
chainValue[6] ^= Pack.LE_To_UInt64(m_personalization, 0);
chainValue[7] ^= Pack.LE_To_UInt64(m_personalization, 8);
}
}
public void Update(byte b)
{
if (128 - bufferPos == 0) {
t0 += 128;
if (t0 == 0)
t1++;
Compress(buffer, 0);
Array.Clear(buffer, 0, buffer.Length);
buffer[0] = b;
bufferPos = 1;
} else {
buffer[bufferPos] = b;
bufferPos++;
}
}
public void BlockUpdate(byte[] message, int offset, int len)
{
if (message != null && len != 0) {
int num = 0;
if (bufferPos != 0) {
num = 128 - bufferPos;
if (num >= len) {
Array.Copy(message, offset, buffer, bufferPos, len);
bufferPos += len;
return;
}
Array.Copy(message, offset, buffer, bufferPos, num);
t0 += 128;
if (t0 == 0)
t1++;
Compress(buffer, 0);
bufferPos = 0;
Array.Clear(buffer, 0, buffer.Length);
}
int num2 = offset + len - 128;
int i;
for (i = offset + num; i < num2; i += 128) {
t0 += 128;
if (t0 == 0)
t1++;
Compress(message, i);
}
Array.Copy(message, i, buffer, 0, offset + len - i);
bufferPos += offset + len - i;
}
}
public int DoFinal(byte[] output, int outOffset)
{
Check.OutputLength(output, outOffset, digestLength, "output buffer too short");
f0 = ulong.MaxValue;
t0 += (ulong)bufferPos;
if (bufferPos > 0 && t0 == 0)
t1++;
Compress(buffer, 0);
Array.Clear(buffer, 0, buffer.Length);
int num = digestLength >> 3;
int num2 = digestLength & 7;
Pack.UInt64_To_LE(chainValue, 0, num, output, outOffset);
if (num2 > 0) {
byte[] array = new byte[8];
Pack.UInt64_To_LE(chainValue[num], array, 0);
Array.Copy(array, 0, output, outOffset + digestLength - num2, num2);
}
Reset();
return digestLength;
}
public void Reset()
{
bufferPos = 0;
f0 = 0;
t0 = 0;
t1 = 0;
Array.Clear(buffer, 0, buffer.Length);
Init();
}
private void Compress(byte[] message, int messagePos)
{
ulong[] array = new ulong[16];
Pack.LE_To_UInt64(message, messagePos, array);
ulong a = chainValue[0];
ulong a2 = chainValue[1];
ulong a3 = chainValue[2];
ulong a4 = chainValue[3];
ulong b = chainValue[4];
ulong b2 = chainValue[5];
ulong b3 = chainValue[6];
ulong b4 = chainValue[7];
ulong c = IV[0];
ulong c2 = IV[1];
ulong c3 = IV[2];
ulong c4 = IV[3];
ulong d = IV[4] ^ t0;
ulong d2 = IV[5] ^ t1;
ulong d3 = IV[6] ^ f0;
ulong d4 = IV[7];
for (int i = 0; i < 12; i++) {
int num = i * 16;
G(array[Sigma[num]], array[Sigma[num + 1]], ref a, ref b, ref c, ref d);
G(array[Sigma[num + 2]], array[Sigma[num + 3]], ref a2, ref b2, ref c2, ref d2);
G(array[Sigma[num + 4]], array[Sigma[num + 5]], ref a3, ref b3, ref c3, ref d3);
G(array[Sigma[num + 6]], array[Sigma[num + 7]], ref a4, ref b4, ref c4, ref d4);
G(array[Sigma[num + 8]], array[Sigma[num + 9]], ref a, ref b2, ref c3, ref d4);
G(array[Sigma[num + 10]], array[Sigma[num + 11]], ref a2, ref b3, ref c4, ref d);
G(array[Sigma[num + 12]], array[Sigma[num + 13]], ref a3, ref b4, ref c, ref d2);
G(array[Sigma[num + 14]], array[Sigma[num + 15]], ref a4, ref b, ref c2, ref d3);
}
chainValue[0] ^= (a ^ c);
chainValue[1] ^= (a2 ^ c2);
chainValue[2] ^= (a3 ^ c3);
chainValue[3] ^= (a4 ^ c4);
chainValue[4] ^= (b ^ d);
chainValue[5] ^= (b2 ^ d2);
chainValue[6] ^= (b3 ^ d3);
chainValue[7] ^= (b4 ^ d4);
}
public int GetDigestSize()
{
return digestLength;
}
public int GetByteLength()
{
return 128;
}
public void ClearKey()
{
if (m_key != null) {
Array.Clear(m_key, 0, m_key.Length);
Array.Clear(buffer, 0, buffer.Length);
}
}
public void ClearSalt()
{
if (m_salt != null)
Array.Clear(m_salt, 0, m_salt.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void G(ulong m1, ulong m2, ref ulong a, ref ulong b, ref ulong c, ref ulong d)
{
a += b + m1;
d = Longs.RotateRight(d ^ a, 32);
c += d;
b = Longs.RotateRight(b ^ c, 24);
a += b + m2;
d = Longs.RotateRight(d ^ a, 16);
c += d;
b = Longs.RotateRight(b ^ c, 63);
}
}
}