SkeinEngine
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);
}
}
}
}
}