Skip to content

Instantly share code, notes, and snippets.

@mjs3339
Created March 24, 2020 21:32
Show Gist options
  • Save mjs3339/46e9069d6bb5c73106e52a33c2598dc1 to your computer and use it in GitHub Desktop.
Save mjs3339/46e9069d6bb5c73106e52a33c2598dc1 to your computer and use it in GitHub Desktop.
SHA3 Implementation in C#
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// Max size is 768 Bits. Proof: 768 / 8 = 96 * 2 = 192, 200-192=8
/// </summary>
[Serializable]
public class SHA3Managed : HashAlgorithmEx
{
private readonly KeccakSpongeManaged ksm;
public SHA3Managed(int size, ulong[] seed = null) : this(size, 24, seed)
{
}
public SHA3Managed(int size) : this(size, 24)
{
}
public SHA3Managed() : this(512, 24)
{
}
public SHA3Managed(int size, int rounds = 24, ulong[] seed = null)
{
if (rounds > 24)
throw new Exception($"Maximum rounds allowed is {24}");
var MaxBR = (64 >> 3) * 25;
var sizeBytes = size >> 3;
var rateBytes = MaxBR - (sizeBytes << 1);
var MaxSize = ((MaxBR - 8) >> 1) << 3;
if (rateBytes < 8)
throw new Exception($"Maximum size allowed is {MaxSize} Bits with {64} bit Width specified. Specified Size is {size} Bits.");
var outputLength = size >> 3;
ksm = new KeccakSpongeManaged(rateBytes, 200 - rateBytes, KeccakSpongeManaged.KeccakDelimiter, outputLength, seed);
ksm.Rounds = rounds;
HashSizeValue = size;
}
public int HashLength => ksm.HashLength;
public override void Initialize()
{
ksm.Initialize();
}
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
Initialize();
ksm.Absorb(array, ibStart, cbSize);
}
protected override byte[] HashFinal()
{
return ksm.Squeeze();
}
}
/// <summary>
/// **** CHECKED VERSION ****
/// https://en.wikipedia.org/wiki/SHA-3
/// </summary>
[Serializable]
public class KeccakSpongeManaged
{
public const int KeccakDelimiter = 6;
public const int ShakeDelimiter = 31;
private readonly int _delimiter;
private readonly int _outputLength;
private readonly int _rateBytes;
private readonly ulong[] _roundConstants =
{
0x0000000000000001,
0x0000000000008082,
0x800000000000808A,
0x8000000080008000,
0x000000000000808B,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008A,
0x0000000000000088,
0x0000000080008009,
0x000000008000000A,
0x000000008000808B,
0x800000000000008B,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800A,
0x800000008000000A,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008
};
private readonly ulong[] _seed;
private int _blockSize;
private int _input;
private int _output;
private byte[] _result;
private ulong[] _state;
public int HashLength;
public int Rounds = 24;
public KeccakSpongeManaged(int rateBytes, int capacityBytes, int delimiter, int outputLength, ulong[] seed)
{
if (rateBytes + capacityBytes != 200 || (uint) (rateBytes % 8) > 0U)
throw new ArgumentException($"rateBytes {rateBytes} + capacityBytes {capacityBytes}={rateBytes + capacityBytes} must be 200 or {rateBytes} must be a multiple of 8");
_rateBytes = rateBytes;
_delimiter = delimiter;
_outputLength = outputLength;
HashLength = outputLength;
_seed = seed;
}
public void Initialize()
{
_blockSize = 0;
_input = 0;
_output = 0;
_state = new ulong[25];
_result = new byte[_outputLength];
if (_seed != null && _seed[0] != 0)
{
for (var i = 0; i < _seed.Length && i < _state.Length; ++i)
_state[i] = _seed[i];
Permute(_state);
}
}
public void Absorb(byte[] array, int start, int size)
{
while (size > 0)
{
_blockSize = Math.Min(size, _rateBytes);
for (var index = start; index < _blockSize; ++index)
{
var num = Convert.ToByte(Buffer.GetByte(_state, index) ^ array[index + _input]);
Buffer.SetByte(_state, index, num);
}
_input += _blockSize;
size -= _blockSize;
if (_blockSize == _rateBytes)
{
Permute(_state);
_blockSize = 0;
}
}
}
public byte[] Squeeze()
{
Buffer.SetByte(_state, _blockSize, Convert.ToByte(Buffer.GetByte(_state, _blockSize) ^ _delimiter));
if ((_delimiter & 128) != 0 && _blockSize == _rateBytes - 1)
Permute(_state);
Buffer.SetByte(_state, _rateBytes - 1, Convert.ToByte(Buffer.GetByte(_state, _rateBytes - 1) ^ 128));
Permute(_state);
var outputLength = _outputLength;
while (outputLength > 0)
{
_blockSize = Math.Min(outputLength, _rateBytes);
Buffer.BlockCopy(_state, 0, _result, _output, _blockSize);
_output += _blockSize;
outputLength -= _blockSize;
if (outputLength > 0)
Permute(_state);
}
return _result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Permute(ulong[] state)
{
for (var round = 0; round < Rounds; ++round)
{
Theta(out var C0, state, out var C1, out var C2, out var C3, out var C4);
RhoPi(state);
Chi(ref C0, state, ref C1, ref C2, ref C3, ref C4);
RC(round, state);
}
}
private static void Theta(out ulong C0, ulong[] state, out ulong C1, out ulong C2, out ulong C3, out ulong C4)
{
C0 = state[0] ^ state[5] ^ state[10] ^ state[15] ^ state[20];
C1 = state[1] ^ state[6] ^ state[11] ^ state[16] ^ state[21];
C2 = state[2] ^ state[7] ^ state[12] ^ state[17] ^ state[22];
C3 = state[3] ^ state[8] ^ state[13] ^ state[18] ^ state[23];
C4 = state[4] ^ state[9] ^ state[14] ^ state[19] ^ state[24];
var D0 = Rol(C1, 1) ^ C4;
var D1 = Rol(C2, 1) ^ C0;
var D2 = Rol(C3, 1) ^ C1;
var D3 = Rol(C4, 1) ^ C2;
var D4 = Rol(C0, 1) ^ C3;
state[0] ^= D0;
state[5] ^= D0;
state[10] ^= D0;
state[15] ^= D0;
state[20] ^= D0;
state[1] ^= D1;
state[6] ^= D1;
state[11] ^= D1;
state[16] ^= D1;
state[21] ^= D1;
state[2] ^= D2;
state[7] ^= D2;
state[12] ^= D2;
state[17] ^= D2;
state[22] ^= D2;
state[3] ^= D3;
state[8] ^= D3;
state[13] ^= D3;
state[18] ^= D3;
state[23] ^= D3;
state[4] ^= D4;
state[9] ^= D4;
state[14] ^= D4;
state[19] ^= D4;
state[24] ^= D4;
}
private static void RhoPi(ulong[] state)
{
var a = Rol(state[1], 1);
state[1] = Rol(state[6], 44);
state[6] = Rol(state[9], 20);
state[9] = Rol(state[22], 61);
state[22] = Rol(state[14], 39);
state[14] = Rol(state[20], 18);
state[20] = Rol(state[2], 62);
state[2] = Rol(state[12], 43);
state[12] = Rol(state[13], 25);
state[13] = Rol(state[19], 8);
state[19] = Rol(state[23], 56);
state[23] = Rol(state[15], 41);
state[15] = Rol(state[4], 27);
state[4] = Rol(state[24], 14);
state[24] = Rol(state[21], 2);
state[21] = Rol(state[8], 55);
state[8] = Rol(state[16], 45);
state[16] = Rol(state[5], 36);
state[5] = Rol(state[3], 28);
state[3] = Rol(state[18], 21);
state[18] = Rol(state[17], 15);
state[17] = Rol(state[11], 10);
state[11] = Rol(state[7], 6);
state[7] = Rol(state[10], 3);
state[10] = a;
}
private void RC(int round, ulong[] state)
{
state[0] ^= _roundConstants[round];
}
private static void Chi(ref ulong C0, ulong[] state, ref ulong C1, ref ulong C2, ref ulong C3, ref ulong C4)
{
for (var index = 0; index < 25; index += 5)
{
C0 = state[index] ^ (~state[1 + index] & state[2 + index]);
C1 = state[1 + index] ^ (~state[2 + index] & state[3 + index]);
C2 = state[2 + index] ^ (~state[3 + index] & state[4 + index]);
C3 = state[3 + index] ^ (~state[4 + index] & state[index]);
C4 = state[4 + index] ^ (~state[index] & state[1 + index]);
state[index] = C0;
state[1 + index] = C1;
state[2 + index] = C2;
state[3 + index] = C3;
state[4 + index] = C4;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Rol(ulong x, byte y)
{
return (x << y) | (x >> (64 - y));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment