KSM128.cs

Re-Work of the Sha3 Keccak Sponge for 128 Bit Hashing

using System;
using System.Runtime.CompilerServices;
public class KSM128 : HashAlgorithmEx
{
    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 KSM128(int size, int rounds = 24, ulong[] seed = null)
    {
        if (rounds > 24)
            throw new Exception($"Maximum rounds allowed is {24}");
        var MaxBR     = (64 >> 3) * 5;
        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 {128} bit Width specified. Specified Size is {size} Bits.");
        var outputLength = size >> 3;
        _rateBytes    = rateBytes;
        _outputLength = outputLength;
        HashLength    = outputLength;
        _seed         = seed;
    }
    public override void Initialize()
    {
        _blockSize = 0;
        _input     = 0;
        _output    = 0;
        _state     = new ulong[5];
        _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)     ^ 6));
        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);
            Iota(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[4];
        C1 = state[1] ^ state[0];
        C2 = state[2] ^ state[1];
        C3 = state[3] ^ state[2];
        C4 = state[4] ^ state[3];
        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[1] ^= D1;
        state[2] ^= D2;
        state[3] ^= D3;
        state[4] ^= D4;
    }
    private static void RhoPi(ulong[] state)
    {
        var a = Rol(state[1], 1);
        state[1] = Rol(state[4], 44);
        state[4] = Rol(state[3], 43);
        state[3] = Rol(state[2], 14);
        state[4] = Rol(state[1], 21);
        state[2] = a;
    }
    private void Iota(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)
    {
        C0       = state[0] ^ (~state[1] & state[2]);
        C1       = state[1] ^ (~state[2] & state[3]);
        C2       = state[2] ^ (~state[3] & state[4]);
        C3       = state[3] ^ (~state[4] & state[0]);
        C4       = state[4] ^ (~state[0] & state[1]);
        state[0] = C0;
        state[1] = C1;
        state[2] = C2;
        state[3] = C3;
        state[4] = C4;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static ulong Rol(ulong x, byte y)
    {
        return (x << y) | (x >> (64 - y));
    }
    protected override void HashCore(byte[] array, int ibStart, int cbSize)
    {
        Initialize();
        Absorb(array, ibStart, cbSize);
    }
    protected override byte[] HashFinal()
    {
        return Squeeze();
    }
}

ArrayMixer.cs

Uses Sha3 to Shuffle Primitive Arrays

using System;
public class ArrayMixer
{
    private readonly SHA3ModInt _alg;
    private readonly int        _moveSize;
    public ArrayMixer() : this(256, 24)
    {
    }
    public ArrayMixer(int hashSize) : this(hashSize, 24)
    {
    }
    public ArrayMixer(int hashSize, int rounds)
    {
        _alg      = new SHA3ModInt(hashSize, rounds);
        _moveSize = _alg.ComputeHash(2.GetBytes()).Length;
    }
    public byte[] Mix(byte[] buf, int iterations = 1000)
    {
        var bufferSize = buf.Length;
        var lBuffer    = new byte[_moveSize];
        var oBuffer    = new byte[bufferSize];
        for (var i = 0; i < iterations; ++i)
        {
            var bytesSuffled = 0;
            var moveSize     = _moveSize;
            var p            = 0;
            while (true)
            {
                var rBytesShuffle = bufferSize - bytesSuffled;
                if (rBytesShuffle < moveSize)
                    moveSize = rBytesShuffle;
                if (rBytesShuffle <= 0)
                    break;
                Buffer.BlockCopy(buf, p, lBuffer, 0, moveSize);
                lBuffer = _alg.ComputeHash(lBuffer);
                Buffer.BlockCopy(lBuffer, 0, oBuffer, p, moveSize);
                p            += moveSize;
                bytesSuffled += moveSize;
            }
            Buffer.BlockCopy(oBuffer, 0, buf, 0, bufferSize);
        }
        lBuffer.Fill(0);
        oBuffer.Fill(0);
        return buf;
    }
    public ushort[] Mix(ushort[] buf, int iterations = 1000)
    {
        var bb = buf.GetBytes();
        return Mix(bb, iterations).ToUShortArray();
    }
    public uint[] Mix(uint[] buf, int iterations = 1000)
    {
        var bb = buf.GetBytes();
        return Mix(bb, iterations).ToUIntArray();
    }
    public ulong[] Mix(ulong[] buf, int iterations = 1000)
    {
        var bb = buf.GetBytes();
        return Mix(bb, iterations).ToULongArray();
    }
    /// <summary>
    ///     Will round up finalSize to the nearest 8 byte boundary.
    /// </summary>
    /// <param name="ba"></param>
    /// <param name="finalSize"></param>
    /// <returns></returns>
    private static ulong[] ByteArrayToULongArray(byte[] ba, int finalSize)
    {
        var minSize = ba.Length / 8;
        if (finalSize < minSize)
            finalSize = minSize;
        ba = PadULong(ba);
        var os = finalSize / 8;
        if (os * 8 < finalSize)
            os++;
        var result = new ulong[os];
        for (var i = 0; i < ba.Length; i += 8)
            Buffer.BlockCopy(ba, i, result, i, 8);
        return result;
    }
    private static byte[] PadULong(byte[] ba)
    {
        var s = ba.Length % 8;
        switch (s)
        {
            case 0:
                break;
            case 1:
                Array.Resize(ref ba, ba.Length + 7);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                ba[ba.Length - 3] = 0x80;
                ba[ba.Length - 4] = 0x80;
                ba[ba.Length - 5] = 0x80;
                ba[ba.Length - 6] = 0x80;
                ba[ba.Length - 7] = 0x80;
                break;
            case 2:
                Array.Resize(ref ba, ba.Length + 6);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                ba[ba.Length - 3] = 0x80;
                ba[ba.Length - 4] = 0x80;
                ba[ba.Length - 5] = 0x80;
                ba[ba.Length - 6] = 0x80;
                break;
            case 3:
                Array.Resize(ref ba, ba.Length + 5);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                ba[ba.Length - 3] = 0x80;
                ba[ba.Length - 4] = 0x80;
                ba[ba.Length - 5] = 0x80;
                break;
            case 4:
                Array.Resize(ref ba, ba.Length + 4);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                ba[ba.Length - 3] = 0x80;
                ba[ba.Length - 4] = 0x80;
                break;
            case 5:
                Array.Resize(ref ba, ba.Length + 3);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                ba[ba.Length - 3] = 0x80;
                break;
            case 6:
                Array.Resize(ref ba, ba.Length + 2);
                ba[ba.Length - 1] = 0x80;
                ba[ba.Length - 2] = 0x80;
                break;
            case 7:
                Array.Resize(ref ba, ba.Length + 1);
                ba[ba.Length - 1] = 0x80;
                break;
        }
        return ba;
    }
    private static void Expand(ulong[] x, int iterations = 1)
    {
        var size = x.Length;
        for (var k = 0; k < iterations; ++k)
        for (var i = 0; i < size; ++i)
        {
            ulong n = 0;
            var   j = 0;
            while (j < size)
            {
                n ^= x[j];
                ++j;
            }
            x[i] = (n << 1) | (n >> 56);
        }
    }
    /// <summary>
    ///     ExpandAndMixArray resizes the array by extrusion then mixes the array using a Sha3 one way hash
    /// </summary>
    /// <param name="ba">The buffer</param>
    /// <param name="size">The final desired size</param>
    /// <param name="iterations">The number of iterations to mix the final array</param>
    public byte[] ExpandAndMixArray(byte[] ba, int size, int iterations = 1000)
    {
        var ula = ByteArrayToULongArray(ba, size);
        Expand(ula, 1);
        var array = ula.GetBytes();
        return Mix(array, iterations);
    }
    public ushort[] ExpandAndMixArray(ushort[] ba, int size, int iterations = 1000)
    {
        var bb = ba.GetBytes();
        return ExpandAndMixArray(bb, size, iterations).ToUShortArray();
    }
    public uint[] ExpandAndMixArray(uint[] ba, int size, int iterations = 1000)
    {
        var bb = ba.GetBytes();
        return ExpandAndMixArray(bb, size, iterations).ToUIntArray();
    }
    public ulong[] ExpandAndMixArray(ulong[] ba, int size, int iterations = 1000)
    {
        var bb = ba.GetBytes();
        return ExpandAndMixArray(bb, size, iterations).ToULongArray();
    }
}

PasswordStretch.cs

Password Stretch using Sha3

Example Code:
var b1   = new byte[0];
        var b2   = new byte[0];
        var salt = new byte[0];
        using (var db = new PasswordStretch("1234567890".ToSecureString(), 16))
        {

            b1   = (byte[]) db.GetBytes(0, 64).Clone();
            salt = db.Salt;
        }

        using (var db = new PasswordStretch("1234567891".ToSecureString(), salt))
        {

            b2 = (byte[]) db.GetBytes(0, 64).Clone();
        }
using System;
using System.Security;
using System.Security.Cryptography;
public class PasswordStretch : IDisposable
{
    private const    int         PacketCount = 4;
    private readonly byte[]      _buffer;
    private readonly byte[]      _salt;
    private readonly SHA3Managed _sha;
    public PasswordStretch(SecureString password) : this(password, 0, 1000)
    {
    }
    public PasswordStretch(SecureString password, int saltSize) : this(password, saltSize, 1000)
    {
    }
    public PasswordStretch(SecureString password, int saltSize, int iterations)
    {
        Iterations = iterations;
        var temp = new byte[0];
        if (saltSize != 0)
        {
            var data = new byte[saltSize];
            new RNGCryptoServiceProvider().GetBytes(data);
            _salt   = data;
            _sha    = new SHA3Managed(512);
            _buffer = new byte[_sha.HashLength * PacketCount];
            _sha.TransformBlock(_salt, 0, _salt.Length, null, 0);
            var pwb = password.GetBytes();
            _sha.TransformBlock(pwb, 0, pwb.Length, null, 0);
            _sha.Finalize();
            temp = _sha.HashValue;
        }
        else
        {
            _sha    = new SHA3Managed(512);
            _buffer = new byte[_sha.HashLength * PacketCount];
            temp    = _sha.ComputeHash(password.GetBytes());
        }
        for (var i = 0; i < PacketCount; i++)
        {
            for (var j = 0; j < Iterations; j++)
                temp = _sha.ComputeHash(temp);
            Buffer.BlockCopy(temp, 0, _buffer, i * _sha.HashLength, _sha.HashLength);
        }
    }
    public PasswordStretch(SecureString password, byte[] salt) : this(password, salt, 1000)
    {
    }
    public PasswordStretch(SecureString password, byte[] salt, int iterations = 1000)
    {
        Iterations = iterations;
        _sha       = new SHA3Managed(512);
        _buffer    = new byte[_sha.HashLength * PacketCount];
        _sha.TransformBlock(salt, 0, salt.Length, null, 0);
        var pwb = password.GetBytes();
        _sha.TransformBlock(pwb, 0, pwb.Length, null, 0);
        _sha.Finalize();
        var temp = _sha.HashValue;
        for (var i = 0; i < PacketCount; i++)
        {
            for (var j = 0; j < Iterations; j++)
                temp = _sha.ComputeHash(temp);
            Buffer.BlockCopy(temp, 0, _buffer, i * _sha.HashLength, _sha.HashLength);
        }
    }
    public byte[] Salt
    {
        get
        {
            if (_salt != null)
                return (byte[]) _salt.Clone();
            return default;
        }
    }
    public int Iterations
    {
        get;
    } = 1000;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    ~PasswordStretch()
    {
        Dispose();
    }
    public byte[] GetBytes(int offset, int psize)
    {
        if (offset + psize > _buffer.Length)
            throw new Exception("Offset and Size Exceed Buffer Length.");
        var passpart = new byte[psize];
        Buffer.BlockCopy(_buffer, offset, passpart, 0, passpart.Length);
        return passpart;
    }
    private void Dispose(bool disposing)
    {
        if (!disposing)
            return;
        if (_sha != null)
            _sha.Dispose();
        if (_buffer != null)
            Array.Clear(_buffer, 0, _buffer.Length);
        if (_salt == null)
            return;
        Array.Clear(_salt, 0, _salt.Length);
    }
}