#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) #pragma warning disable using System; using System.Collections; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests { /// /// Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block /// sizes, based on the Threefish tweakable block cipher. /// /// /// This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 /// competition in October 2010. ///

/// Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir /// Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. ///

/// This implementation is the basis for and , implementing the /// parameter based configuration system that allows Skein to be adapted to multiple applications.
/// Initialising the engine with allows standard and arbitrary parameters to /// be applied during the Skein hash function. ///

/// Implemented: ///

///

/// Not implemented: ///

///
/// public class SkeinEngine : IMemoable { /// /// 256 bit block size - Skein-256 /// public const int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256; /// /// 512 bit block size - Skein-512 /// public const int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512; /// /// 1024 bit block size - Skein-1024 /// public const int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024; // Minimal at present, but more complex when tree hashing is implemented private class Configuration { private byte[] bytes = new byte[32]; public Configuration(long outputSizeBits) { // 0..3 = ASCII SHA3 bytes[0] = (byte)'S'; bytes[1] = (byte)'H'; bytes[2] = (byte)'A'; bytes[3] = (byte)'3'; // 4..5 = version number in LSB order bytes[4] = 1; bytes[5] = 0; // 8..15 = output length ThreefishEngine.WordToBytes((ulong)outputSizeBits, bytes, 8); } public byte[] Bytes { get { return bytes; } } } public class Parameter { private int type; private byte[] value; public Parameter(int type, byte[] value) { this.type = type; this.value = value; } public int Type { get { return type; } } public byte[] Value { get { return value; } } } /** * The parameter type for the Skein key. */ private const int PARAM_TYPE_KEY = 0; /** * The parameter type for the Skein configuration block. */ private const int PARAM_TYPE_CONFIG = 4; /** * The parameter type for the message. */ private const int PARAM_TYPE_MESSAGE = 48; /** * The parameter type for the output transformation. */ private const int PARAM_TYPE_OUTPUT = 63; /** * Precalculated UBI(CFG) states for common state/output combinations without key or other * pre-message params. */ private static readonly IDictionary INITIAL_STATES = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable(); static SkeinEngine() { // From Appendix C of the Skein 1.3 NIST submission InitialState(SKEIN_256, 128, new ulong[]{ 0xe1111906964d7260UL, 0x883daaa77c8d811cUL, 0x10080df491960f7aUL, 0xccf7dde5b45bc1c2UL}); InitialState(SKEIN_256, 160, new ulong[]{ 0x1420231472825e98UL, 0x2ac4e9a25a77e590UL, 0xd47a58568838d63eUL, 0x2dd2e4968586ab7dUL}); InitialState(SKEIN_256, 224, new ulong[]{ 0xc6098a8c9ae5ea0bUL, 0x876d568608c5191cUL, 0x99cb88d7d7f53884UL, 0x384bddb1aeddb5deUL}); InitialState(SKEIN_256, 256, new ulong[]{ 0xfc9da860d048b449UL, 0x2fca66479fa7d833UL, 0xb33bc3896656840fUL, 0x6a54e920fde8da69UL}); InitialState(SKEIN_512, 128, new ulong[]{ 0xa8bc7bf36fbf9f52UL, 0x1e9872cebd1af0aaUL, 0x309b1790b32190d3UL, 0xbcfbb8543f94805cUL, 0x0da61bcd6e31b11bUL, 0x1a18ebead46a32e3UL, 0xa2cc5b18ce84aa82UL, 0x6982ab289d46982dUL}); InitialState(SKEIN_512, 160, new ulong[]{ 0x28b81a2ae013bd91UL, 0xc2f11668b5bdf78fUL, 0x1760d8f3f6a56f12UL, 0x4fb747588239904fUL, 0x21ede07f7eaf5056UL, 0xd908922e63ed70b8UL, 0xb8ec76ffeccb52faUL, 0x01a47bb8a3f27a6eUL}); InitialState(SKEIN_512, 224, new ulong[]{ 0xccd0616248677224UL, 0xcba65cf3a92339efUL, 0x8ccd69d652ff4b64UL, 0x398aed7b3ab890b4UL, 0x0f59d1b1457d2bd0UL, 0x6776fe6575d4eb3dUL, 0x99fbc70e997413e9UL, 0x9e2cfccfe1c41ef7UL}); InitialState(SKEIN_512, 384, new ulong[]{ 0xa3f6c6bf3a75ef5fUL, 0xb0fef9ccfd84faa4UL, 0x9d77dd663d770cfeUL, 0xd798cbf3b468fddaUL, 0x1bc4a6668a0e4465UL, 0x7ed7d434e5807407UL, 0x548fc1acd4ec44d6UL, 0x266e17546aa18ff8UL}); InitialState(SKEIN_512, 512, new ulong[]{ 0x4903adff749c51ceUL, 0x0d95de399746df03UL, 0x8fd1934127c79bceUL, 0x9a255629ff352cb1UL, 0x5db62599df6ca7b0UL, 0xeabe394ca9d5c3f4UL, 0x991112c71a75b523UL, 0xae18a40b660fcc33UL}); } private static void InitialState(int blockSize, int outputSize, ulong[] state) { INITIAL_STATES.Add(VariantIdentifier(blockSize / 8, outputSize / 8), state); } private static int VariantIdentifier(int blockSizeBytes, int outputSizeBytes) { return (outputSizeBytes << 16) | blockSizeBytes; } private class UbiTweak { /** * Point at which position might overflow long, so switch to add with carry logic */ private const ulong LOW_RANGE = UInt64.MaxValue - UInt32.MaxValue; /** * Bit 127 = final */ private const ulong T1_FINAL = 1UL << 63; /** * Bit 126 = first */ private const ulong T1_FIRST = 1UL << 62; /** * UBI uses a 128 bit tweak */ private ulong[] tweak = new ulong[2]; /** * Whether 64 bit position exceeded */ private bool extendedPosition; public UbiTweak() { Reset(); } public void Reset(UbiTweak tweak) { this.tweak = Arrays.Clone(tweak.tweak, this.tweak); this.extendedPosition = tweak.extendedPosition; } public void Reset() { tweak[0] = 0; tweak[1] = 0; extendedPosition = false; First = true; } public uint Type { get { return (uint)((tweak[1] >> 56) & 0x3FUL); } set { // Bits 120..125 = type tweak[1] = (tweak[1] & 0xFFFFFFC000000000UL) | ((value & 0x3FUL) << 56); } } public bool First { get { return ((tweak[1] & T1_FIRST) != 0); } set { if (value) { tweak[1] |= T1_FIRST; } else { tweak[1] &= ~T1_FIRST; } } } public bool Final { get { return ((tweak[1] & T1_FINAL) != 0); } set { if (value) { tweak[1] |= T1_FINAL; } else { tweak[1] &= ~T1_FINAL; } } } /** * Advances the position in the tweak by the specified value. */ public void AdvancePosition(int advance) { // Bits 0..95 = position if (extendedPosition) { ulong[] parts = new ulong[3]; parts[0] = tweak[0] & 0xFFFFFFFFUL; parts[1] = (tweak[0] >> 32) & 0xFFFFFFFFUL; parts[2] = tweak[1] & 0xFFFFFFFFUL; ulong carry = (ulong)advance; for (int i = 0; i < parts.Length; i++) { carry += parts[i]; parts[i] = carry; carry >>= 32; } tweak[0] = ((parts[1] & 0xFFFFFFFFUL) << 32) | (parts[0] & 0xFFFFFFFFUL); tweak[1] = (tweak[1] & 0xFFFFFFFF00000000UL) | (parts[2] & 0xFFFFFFFFUL); } else { ulong position = tweak[0]; position += (uint)advance; tweak[0] = position; if (position > LOW_RANGE) { extendedPosition = true; } } } public ulong[] GetWords() { return tweak; } public override string ToString() { return Type + " first: " + First + ", final: " + Final; } } /** * The Unique Block Iteration chaining mode. */ // TODO: This might be better as methods... private class UBI { private readonly UbiTweak tweak = new UbiTweak(); private readonly SkeinEngine engine; /** * Buffer for the current block of message data */ private byte[] currentBlock; /** * Offset into the current message block */ private int currentOffset; /** * Buffer for message words for feedback into encrypted block */ 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, this.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) { /* * Buffer complete blocks for the underlying Threefish cipher, only flushing when there * are subsequent bytes (last block must be processed in doFinal() with final=true set). */ int copied = 0; while (len > copied) { if (currentOffset == currentBlock.Length) { ProcessBlock(output); tweak.First = false; currentOffset = 0; } int toCopy = System.Math.Min((len - copied), currentBlock.Length - currentOffset); Array.Copy(value, offset + copied, currentBlock, currentOffset, toCopy); copied += toCopy; currentOffset += toCopy; tweak.AdvancePosition(toCopy); } } private void ProcessBlock(ulong[] output) { engine.threefish.Init(true, engine.chain, tweak.GetWords()); for (int i = 0; i < message.Length; i++) { message[i] = ThreefishEngine.BytesToWord(currentBlock, i * 8); } engine.threefish.ProcessBlock(message, output); for (int i = 0; i < output.Length; i++) { output[i] ^= message[i]; } } public void DoFinal(ulong[] output) { // Pad remainder of current block with zeroes for (int i = currentOffset; i < currentBlock.Length; i++) { currentBlock[i] = 0; } tweak.Final = true; ProcessBlock(output); } } /** * Underlying Threefish tweakable block cipher */ private readonly ThreefishEngine threefish; /** * Size of the digest output, in bytes */ private readonly int outputSizeBytes; /** * The current chaining/state value */ private ulong[] chain; /** * The initial state value */ private ulong[] initialState; /** * The (optional) key parameter */ private byte[] key; /** * Parameters to apply prior to the message */ private Parameter[] preMessageParameters; /** * Parameters to apply after the message, but prior to output */ private Parameter[] postMessageParameters; /** * The current UBI operation */ private readonly UBI ubi; /** * Buffer for single byte update method */ private readonly byte[] singleByte = new byte[1]; /// /// Constructs a Skein digest with an internal state size and output size. /// /// the internal state size in bits - one of or /// . /// the output/digest size to produce in bits, which must be an integral number of /// bytes. public SkeinEngine(int blockSizeBits, int outputSizeBits) { if (outputSizeBits % 8 != 0) { throw new ArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits); } // TODO: Prevent digest sizes > block size? this.outputSizeBytes = outputSizeBits / 8; this.threefish = new ThreefishEngine(blockSizeBits); this.ubi = new UBI(this,threefish.GetBlockSize()); } /// /// Creates a SkeinEngine as an exact copy of an existing instance. /// public SkeinEngine(SkeinEngine engine) : this(engine.BlockSize * 8, engine.OutputSize * 8) { CopyIn(engine); } private void CopyIn(SkeinEngine engine) { this.ubi.Reset(engine.ubi); this.chain = Arrays.Clone(engine.chain, this.chain); this.initialState = Arrays.Clone(engine.initialState, this.initialState); this.key = Arrays.Clone(engine.key, this.key); this.preMessageParameters = Clone(engine.preMessageParameters, this.preMessageParameters); this.postMessageParameters = Clone(engine.postMessageParameters, this.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 s = (SkeinEngine)other; if ((BlockSize != s.BlockSize) || (outputSizeBytes != s.outputSizeBytes)) { throw new MemoableResetException("Incompatible parameters in provided SkeinEngine."); } CopyIn(s); } public int OutputSize { get { return outputSizeBytes; } } public int BlockSize { get { return threefish.GetBlockSize (); } } /// /// Initialises the Skein engine with the provided parameters. See for /// details on the parameterisation of the Skein hash function. /// /// the parameters to apply to this engine, or null to use no parameters. public void Init(SkeinParameters parameters) { this.chain = null; this.key = null; this.preMessageParameters = null; this.postMessageParameters = null; if (parameters != null) { byte[] key = parameters.GetKey(); if (key.Length < 16) { throw new ArgumentException("Skein key must be at least 128 bits."); } InitParams(parameters.GetParameters()); } CreateInitialState(); // Initialise message block UbiInit(PARAM_TYPE_MESSAGE); } private void InitParams(IDictionary parameters) { IEnumerator keys = parameters.Keys.GetEnumerator(); IList pre = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList(); IList post = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList(); while (keys.MoveNext()) { int type = (int)keys.Current; byte[] value = (byte[])parameters[type]; if (type == PARAM_TYPE_KEY) { this.key = value; } else if (type < PARAM_TYPE_MESSAGE) { pre.Add(new Parameter(type, value)); } else { post.Add(new Parameter(type, value)); } } preMessageParameters = new Parameter[pre.Count]; pre.CopyTo(preMessageParameters, 0); Array.Sort(preMessageParameters); postMessageParameters = new Parameter[post.Count]; post.CopyTo(postMessageParameters, 0); Array.Sort(postMessageParameters); } /** * Calculate the initial (pre message block) chaining state. */ private void CreateInitialState() { ulong[] precalc = (ulong[])INITIAL_STATES[VariantIdentifier(BlockSize, OutputSize)]; if ((key == null) && (precalc != null)) { // Precalculated UBI(CFG) chain = Arrays.Clone(precalc); } else { // Blank initial state chain = new ulong[BlockSize / 8]; // Process key block if (key != null) { UbiComplete(SkeinParameters.PARAM_TYPE_KEY, key); } // Process configuration block UbiComplete(PARAM_TYPE_CONFIG, new Configuration(outputSizeBytes * 8).Bytes); } // Process additional pre-message parameters if (preMessageParameters != null) { for (int i = 0; i < preMessageParameters.Length; i++) { Parameter param = preMessageParameters[i]; UbiComplete(param.Type, param.Value); } } initialState = Arrays.Clone(chain); } /// /// Reset the engine to the initial state (with the key and any pre-message parameters , ready to /// accept message input. /// public void Reset() { Array.Copy(initialState, 0, chain, 0, chain.Length); UbiInit(PARAM_TYPE_MESSAGE); } private void UbiComplete(int type, byte[] value) { UbiInit(type); this.ubi.Update(value, 0, value.Length, chain); UbiFinal(); } private void UbiInit(int type) { this.ubi.Reset(type); } private void UbiFinal() { ubi.DoFinal(chain); } private void CheckInitialised() { if (this.ubi == null) { throw new ArgumentException("Skein engine is not initialised."); } } public void Update(byte inByte) { singleByte[0] = inByte; Update(singleByte, 0, 1); } public void Update(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"); } // Finalise message block UbiFinal(); // Process additional post-message parameters if (postMessageParameters != null) { for (int i = 0; i < postMessageParameters.Length; i++) { Parameter param = postMessageParameters[i]; UbiComplete(param.Type, param.Value); } } // Perform the output transform int blockSize = BlockSize; int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize); for (int i = 0; i < blocksRequired; i++) { int toWrite = System.Math.Min(blockSize, outputSizeBytes - (i * blockSize)); Output((ulong)i, outBytes, outOff + (i * blockSize), toWrite); } Reset(); return outputSizeBytes; } private void Output(ulong outputSequence, byte[] outBytes, int outOff, int outputBytes) { byte[] currentBytes = new byte[8]; ThreefishEngine.WordToBytes(outputSequence, currentBytes, 0); // Output is a sequence of UBI invocations all of which use and preserve the pre-output // state ulong[] outputWords = new ulong[chain.Length]; UbiInit(PARAM_TYPE_OUTPUT); this.ubi.Update(currentBytes, 0, currentBytes.Length, outputWords); ubi.DoFinal(outputWords); int wordsRequired = ((outputBytes + 8 - 1) / 8); for (int i = 0; i < wordsRequired; i++) { int toWrite = System.Math.Min(8, outputBytes - (i * 8)); if (toWrite == 8) { ThreefishEngine.WordToBytes(outputWords[i], outBytes, outOff + (i * 8)); } else { ThreefishEngine.WordToBytes(outputWords[i], currentBytes, 0); Array.Copy(currentBytes, 0, outBytes, outOff + (i * 8), toWrite); } } } } } #pragma warning restore #endif