<PackageReference Include="BouncyCastle.Cryptography" Version="2.7.0-beta.98" />

SkeinEngine

public class SkeinEngine : IMemoable
Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block sizes, based on the ThreefishEngineThreefish tweakable block cipher.
using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Collections; using System; using System.Collections.Generic; namespace Org.BouncyCastle.Crypto.Digests { public class SkeinEngine : IMemoable { private class Configuration { private byte[] bytes = new byte[32]; public byte[] Bytes => bytes; public Configuration(long outputSizeBits) { bytes[0] = 83; bytes[1] = 72; bytes[2] = 65; bytes[3] = 51; bytes[4] = 1; bytes[5] = 0; Pack.UInt64_To_LE((ulong)outputSizeBits, bytes, 8); } } public class Parameter { private int type; private byte[] value; public int Type => type; public byte[] Value => value; public Parameter(int type, byte[] value) { this.type = type; this.value = value; } } private class UbiTweak { private const ulong LOW_RANGE = 18446744069414584320; private const ulong T1_FINAL = 9223372036854775808; private const ulong T1_FIRST = 4611686018427387904; private ulong[] tweak = new ulong[2]; private bool extendedPosition; public uint Type { get { return (uint)((tweak[1] >> 56) & 63); } set { tweak[1] = (ulong)(((long)tweak[1] & -274877906944) | (((long)value & 63) << 56)); } } public bool First { get { return (tweak[1] & 4611686018427387904) != 0; } set { if (value) tweak[1] |= 4611686018427387904; else tweak[1] &= 13835058055282163711; } } public bool Final { get { return ((long)tweak[1] & -9223372036854775808) != 0; } set { if (value) tweak[1] |= 9223372036854775808; else tweak[1] &= 9223372036854775807; } } public UbiTweak() { Reset(); } public void Reset(UbiTweak tweak) { this.tweak = Arrays.Clone(tweak.tweak, this.tweak); extendedPosition = tweak.extendedPosition; } public void Reset() { tweak[0] = 0; tweak[1] = 0; extendedPosition = false; First = true; } public void AdvancePosition(int advance) { if (extendedPosition) { ulong[] array = new ulong[3] { tweak[0] & uint.MaxValue, (tweak[0] >> 32) & uint.MaxValue, tweak[1] & uint.MaxValue }; ulong num = (ulong)advance; for (int i = 0; i < array.Length; i++) { num = (array[i] = num + array[i]) >> 32; } tweak[0] = (((array[1] & uint.MaxValue) << 32) | (array[0] & uint.MaxValue)); tweak[1] = (ulong)(((long)tweak[1] & -4294967296) | (long)(array[2] & uint.MaxValue)); } else { ulong num2 = tweak[0]; num2 += (uint)advance; tweak[0] = num2; if (num2 > 18446744069414584320) extendedPosition = true; } } public ulong[] GetWords() { return tweak; } public override string ToString() { string[] obj = new string[5] { Type.ToString(), " first: ", null, null, null }; bool flag = First; obj[2] = flag.ToString(); obj[3] = ", final: "; flag = Final; obj[4] = flag.ToString(); return string.Concat(obj); } } private class UBI { private readonly UbiTweak tweak = new UbiTweak(); private readonly SkeinEngine engine; private byte[] currentBlock; private int currentOffset; private ulong[] message; public UBI(SkeinEngine engine, int blockSize) { this.engine = engine; currentBlock = new byte[blockSize]; message = new ulong[currentBlock.Length / 8]; } public void Reset(UBI ubi) { currentBlock = Arrays.Clone(ubi.currentBlock, currentBlock); currentOffset = ubi.currentOffset; message = Arrays.Clone(ubi.message, message); tweak.Reset(ubi.tweak); } public void Reset(int type) { tweak.Reset(); tweak.Type = (uint)type; currentOffset = 0; } public void Update(byte[] value, int offset, int len, ulong[] output) { int num = 0; while (len > num) { if (currentOffset == currentBlock.Length) { ProcessBlock(output); tweak.First = false; currentOffset = 0; } int num2 = System.Math.Min(len - num, currentBlock.Length - currentOffset); Array.Copy(value, offset + num, currentBlock, currentOffset, num2); num += num2; currentOffset += num2; tweak.AdvancePosition(num2); } } private void ProcessBlock(ulong[] output) { engine.threefish.Init(true, engine.chain, tweak.GetWords()); Pack.LE_To_UInt64(currentBlock, 0, message); engine.threefish.ProcessBlock(message, output); for (int i = 0; i < output.Length; i++) { output[i] ^= message[i]; } } public void DoFinal(ulong[] output) { for (int i = currentOffset; i < currentBlock.Length; i++) { currentBlock[i] = 0; } tweak.Final = true; ProcessBlock(output); } } public const int SKEIN_256 = 256; public const int SKEIN_512 = 512; public const int SKEIN_1024 = 1024; private const int PARAM_TYPE_KEY = 0; private const int PARAM_TYPE_CONFIG = 4; private const int PARAM_TYPE_MESSAGE = 48; private const int PARAM_TYPE_OUTPUT = 63; private static readonly IDictionary<int, ulong[]> InitialStates; private readonly ThreefishEngine threefish; private readonly int outputSizeBytes; private ulong[] chain; private ulong[] initialState; private byte[] key; private Parameter[] preMessageParameters; private Parameter[] postMessageParameters; private readonly UBI ubi; private readonly byte[] singleByte = new byte[1]; public int OutputSize => outputSizeBytes; public int BlockSize => threefish.GetBlockSize(); static SkeinEngine() { InitialStates = new Dictionary<int, ulong[]>(); InitialState(256, 128, new ulong[4] { 16217771249220022880, 9817190399063458076, 1155188648486244218, 14769517481627992514 }); InitialState(256, 160, new ulong[4] { 1450197650740764312, 3081844928540042640, 15310647011875280446, 3301952811952417661 }); InitialState(256, 224, new ulong[4] { 14270089230798940683, 9758551101254474012, 11082101768697755780, 4056579644589979102 }); InitialState(256, 256, new ulong[4] { 18202890402666165321, 3443677322885453875, 12915131351309911055, 7662005193972177513 }); InitialState(512, 128, new ulong[8] { 12158729379475595090, 2204638249859346602, 3502419045458743507, 13617680570268287068, 983504137758028059, 1880512238245786339, 11730851291495443074, 7602827311880509485 }); InitialState(512, 160, new ulong[8] { 2934123928682216849, 14047033351726823311, 1684584802963255058, 5744138295201861711, 2444857010922934358, 15638910433986703544, 13325156239043941114, 118355523173251694 }); InitialState(512, 224, new ulong[8] { 14758403053642543652, 14674518637417806319, 10145881904771976036, 4146387520469897396, 1106145742801415120, 7455425944880474941, 11095680972475339753, 11397762726744039159 }); InitialState(512, 384, new ulong[8] { 11814849197074935647, 12753905853581818532, 11346781217370868990, 15535391162178797018, 2000907093792408677, 9140007292425499655, 6093301768906360022, 2769176472213098488 }); InitialState(512, 512, new ulong[8] { 5261240102383538638, 978932832955457283, 10363226125605772238, 11107378794354519217, 6752626034097301424, 16915020251879818228, 11029617608758768931, 12544957130904423475 }); } private static void InitialState(int blockSize, int outputSize, ulong[] state) { InitialStates.Add(VariantIdentifier(blockSize / 8, outputSize / 8), state); } private static int VariantIdentifier(int blockSizeBytes, int outputSizeBytes) { return (outputSizeBytes << 16) | blockSizeBytes; } public SkeinEngine(int blockSizeBits, int outputSizeBits) { if (outputSizeBits % 8 != 0) throw new ArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits.ToString()); outputSizeBytes = outputSizeBits / 8; threefish = new ThreefishEngine(blockSizeBits); ubi = new UBI(this, threefish.GetBlockSize()); } public SkeinEngine(SkeinEngine engine) : this(engine.BlockSize * 8, engine.OutputSize * 8) { CopyIn(engine); } private void CopyIn(SkeinEngine engine) { ubi.Reset(engine.ubi); chain = Arrays.Clone(engine.chain, chain); initialState = Arrays.Clone(engine.initialState, initialState); key = Arrays.Clone(engine.key, key); preMessageParameters = Clone(engine.preMessageParameters, preMessageParameters); postMessageParameters = Clone(engine.postMessageParameters, postMessageParameters); } private static Parameter[] Clone(Parameter[] data, Parameter[] existing) { if (data == null) return null; if (existing == null || existing.Length != data.Length) existing = new Parameter[data.Length]; Array.Copy(data, 0, existing, 0, existing.Length); return existing; } public IMemoable Copy() { return new SkeinEngine(this); } public void Reset(IMemoable other) { SkeinEngine skeinEngine = (SkeinEngine)other; if (BlockSize != skeinEngine.BlockSize || outputSizeBytes != skeinEngine.outputSizeBytes) throw new MemoableResetException("Incompatible parameters in provided SkeinEngine."); CopyIn(skeinEngine); } public void Init(SkeinParameters parameters) { chain = null; key = null; preMessageParameters = null; postMessageParameters = null; if (parameters != null) { if (parameters.GetKey().Length < 16) throw new ArgumentException("Skein key must be at least 128 bits."); InitParams(parameters.GetParameters()); } CreateInitialState(); UbiInit(48); } private void InitParams(IDictionary<int, byte[]> parameters) { List<Parameter> list = new List<Parameter>(); List<Parameter> list2 = new List<Parameter>(); foreach (KeyValuePair<int, byte[]> parameter in parameters) { int num = parameter.Key; byte[] value = parameter.Value; if (num == 0) key = value; else if (num < 48) { list.Add(new Parameter(num, value)); } else { list2.Add(new Parameter(num, value)); } } preMessageParameters = new Parameter[list.Count]; list.CopyTo(preMessageParameters, 0); Array.Sort(preMessageParameters); postMessageParameters = new Parameter[list2.Count]; list2.CopyTo(postMessageParameters, 0); Array.Sort(postMessageParameters); } private void CreateInitialState() { ulong[] valueOrNull = CollectionUtilities.GetValueOrNull(InitialStates, VariantIdentifier(BlockSize, OutputSize)); if (key == null && valueOrNull != null) chain = Arrays.Clone(valueOrNull); else { chain = new ulong[BlockSize / 8]; if (key != null) UbiComplete(0, key); UbiComplete(4, new Configuration(outputSizeBytes * 8).Bytes); } if (preMessageParameters != null) { for (int i = 0; i < preMessageParameters.Length; i++) { Parameter parameter = preMessageParameters[i]; UbiComplete(parameter.Type, parameter.Value); } } initialState = Arrays.Clone(chain); } public void Reset() { Array.Copy(initialState, 0, chain, 0, chain.Length); UbiInit(48); } private void UbiComplete(int type, byte[] value) { UbiInit(type); ubi.Update(value, 0, value.Length, chain); UbiFinal(); } private void UbiInit(int type) { ubi.Reset(type); } private void UbiFinal() { ubi.DoFinal(chain); } private void CheckInitialised() { if (ubi == null) throw new ArgumentException("Skein engine is not initialised."); } public void Update(byte inByte) { singleByte[0] = inByte; BlockUpdate(singleByte, 0, 1); } public void BlockUpdate(byte[] inBytes, int inOff, int len) { CheckInitialised(); ubi.Update(inBytes, inOff, len, chain); } public int DoFinal(byte[] outBytes, int outOff) { CheckInitialised(); if (outBytes.Length < outOff + outputSizeBytes) throw new DataLengthException("Output buffer is too short to hold output"); UbiFinal(); if (postMessageParameters != null) { for (int i = 0; i < postMessageParameters.Length; i++) { Parameter parameter = postMessageParameters[i]; UbiComplete(parameter.Type, parameter.Value); } } int blockSize = BlockSize; int num = (outputSizeBytes + blockSize - 1) / blockSize; for (int j = 0; j < num; j++) { int outputBytes = System.Math.Min(blockSize, outputSizeBytes - j * blockSize); Output((ulong)j, outBytes, outOff + j * blockSize, outputBytes); } Reset(); return outputSizeBytes; } private void Output(ulong outputSequence, byte[] outBytes, int outOff, int outputBytes) { byte[] array = new byte[8]; Pack.UInt64_To_LE(outputSequence, array, 0); ulong[] array2 = new ulong[chain.Length]; UbiInit(63); ubi.Update(array, 0, array.Length, array2); ubi.DoFinal(array2); int num = (outputBytes + 8 - 1) / 8; for (int i = 0; i < num; i++) { int num2 = System.Math.Min(8, outputBytes - i * 8); if (num2 == 8) Pack.UInt64_To_LE(array2[i], outBytes, outOff + i * 8); else { Pack.UInt64_To_LE(array2[i], array, 0); Array.Copy(array, 0, outBytes, outOff + i * 8, num2); } } } } }