package org.bouncycastle.crypto.digests;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.engines.ThreefishEngine;
import org.bouncycastle.crypto.macs.SkeinMac;
import org.bouncycastle.crypto.params.SkeinParameters;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Memoable;
/**
* Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block
* sizes, based on the {@link ThreefishEngine Threefish} tweakable block cipher.
* <p>
* This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3
* competition in October 2010.
* <p>
* Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir
* Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker.
* <p>
* This implementation is the basis for {@link SkeinDigest} and {@link SkeinMac}, implementing the
* parameter based configuration system that allows Skein to be adapted to multiple applications. <br>
* Initialising the engine with {@link SkeinParameters} allows standard and arbitrary parameters to
* be applied during the Skein hash function.
* <p>
* Implemented:
* <ul>
* <li>256, 512 and 1024 bit internal states.</li>
* <li>Full 96 bit input length.</li>
* <li>Parameters defined in the Skein specification, and arbitrary other pre and post message
* parameters.</li>
* <li>Arbitrary output size in 1 byte intervals.</li>
* </ul>
* <p>
* Not implemented:
* <ul>
* <li>Sub-byte length input (bit padding).</li>
* <li>Tree hashing.</li>
* </ul>
*
* @see SkeinParameters
*/
public class SkeinEngine
implements Memoable
{
/**
* 256 bit block size - Skein 256
*/
public static final int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256;
/**
* 512 bit block size - Skein 512
*/
public static final int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512;
/**
* 1024 bit block size - Skein 1024
*/
public static final int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024;
// Minimal at present, but more complex when tree hashing is implemented
private static 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(outputSizeBits, bytes, 8);
}
public byte[] getBytes()
{
return bytes;
}
}
public static class Parameter
{
private int type;
private byte[] value;
public Parameter(int type, byte[] value)
{
this.type = type;
this.value = value;
}
public int getType()
{
return type;
}
public byte[] getValue()
{
return value;
}
}
/**
* The parameter type for the Skein key.
*/
private static final int PARAM_TYPE_KEY = 0;
/**
* The parameter type for the Skein configuration block.
*/
private static final int PARAM_TYPE_CONFIG = 4;
/**
* The parameter type for the message.
*/
private static final int PARAM_TYPE_MESSAGE = 48;
/**
* The parameter type for the output transformation.
*/
private static final int PARAM_TYPE_OUTPUT = 63;
/**
* Precalculated UBI(CFG) states for common state/output combinations without key or other
* pre-message params.
*/
private static final Hashtable INITIAL_STATES = new Hashtable();
static
{
// From Appendix C of the Skein 1.3 NIST submission
initialState(SKEIN_256, 128, new long[]{
0xe1111906964d7260L,
0x883daaa77c8d811cL,
0x10080df491960f7aL,
0xccf7dde5b45bc1c2L});
initialState(SKEIN_256, 160, new long[]{
0x1420231472825e98L,
0x2ac4e9a25a77e590L,
0xd47a58568838d63eL,
0x2dd2e4968586ab7dL});
initialState(SKEIN_256, 224, new long[]{
0xc6098a8c9ae5ea0bL,
0x876d568608c5191cL,
0x99cb88d7d7f53884L,
0x384bddb1aeddb5deL});
initialState(SKEIN_256, 256, new long[]{
0xfc9da860d048b449L,
0x2fca66479fa7d833L,
0xb33bc3896656840fL,
0x6a54e920fde8da69L});
initialState(SKEIN_512, 128, new long[]{
0xa8bc7bf36fbf9f52L,
0x1e9872cebd1af0aaL,
0x309b1790b32190d3L,
0xbcfbb8543f94805cL,
0x0da61bcd6e31b11bL,
0x1a18ebead46a32e3L,
0xa2cc5b18ce84aa82L,
0x6982ab289d46982dL});
initialState(SKEIN_512, 160, new long[]{
0x28b81a2ae013bd91L,
0xc2f11668b5bdf78fL,
0x1760d8f3f6a56f12L,
0x4fb747588239904fL,
0x21ede07f7eaf5056L,
0xd908922e63ed70b8L,
0xb8ec76ffeccb52faL,
0x01a47bb8a3f27a6eL});
initialState(SKEIN_512, 224, new long[]{
0xccd0616248677224L,
0xcba65cf3a92339efL,
0x8ccd69d652ff4b64L,
0x398aed7b3ab890b4L,
0x0f59d1b1457d2bd0L,
0x6776fe6575d4eb3dL,
0x99fbc70e997413e9L,
0x9e2cfccfe1c41ef7L});
initialState(SKEIN_512, 384, new long[]{
0xa3f6c6bf3a75ef5fL,
0xb0fef9ccfd84faa4L,
0x9d77dd663d770cfeL,
0xd798cbf3b468fddaL,
0x1bc4a6668a0e4465L,
0x7ed7d434e5807407L,
0x548fc1acd4ec44d6L,
0x266e17546aa18ff8L});
initialState(SKEIN_512, 512, new long[]{
0x4903adff749c51ceL,
0x0d95de399746df03L,
0x8fd1934127c79bceL,
0x9a255629ff352cb1L,
0x5db62599df6ca7b0L,
0xeabe394ca9d5c3f4L,
0x991112c71a75b523L,
0xae18a40b660fcc33L});
}
private static void initialState(int blockSize, int outputSize, long[] state)
{
INITIAL_STATES.put(variantIdentifier(blockSize / 8, outputSize / 8), state);
}
private static Integer variantIdentifier(int blockSizeBytes, int outputSizeBytes)
{
return new Integer((outputSizeBytes << 16) | blockSizeBytes);
}
private static class UbiTweak
{
/**
* Point at which position might overflow long, so switch to add with carry logic
*/
private static final long LOW_RANGE = Long.MAX_VALUE - Integer.MAX_VALUE;
/**
* Bit 127 = final
*/
private static final long T1_FINAL = 1L << 63;
/**
* Bit 126 = first
*/
private static final long T1_FIRST = 1L << 62;
/**
* UBI uses a 128 bit tweak
*/
private long tweak[] = new long[2];
/**
* Whether 64 bit position exceeded
*/
private boolean 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;
setFirst(true);
}
public void setType(int type)
{
// Bits 120..125 = type
tweak[1] = (tweak[1] & 0xFFFFFFC000000000L) | ((type & 0x3FL) << 56);
}
public int getType()
{
return (int)((tweak[1] >>> 56) & 0x3FL);
}
public void setFirst(boolean first)
{
if (first)
{
tweak[1] |= T1_FIRST;
}
else
{
tweak[1] &= ~T1_FIRST;
}
}
public boolean isFirst()
{
return ((tweak[1] & T1_FIRST) != 0);
}
public void setFinal(boolean last)
{
if (last)
{
tweak[1] |= T1_FINAL;
}
else
{
tweak[1] &= ~T1_FINAL;
}
}
public boolean isFinal()
{
return ((tweak[1] & T1_FINAL) != 0);
}
/**
* Advances the position in the tweak by the specified value.
*/
public void advancePosition(int advance)
{
// Bits 0..95 = position
if (extendedPosition)
{
long[] parts = new long[3];
parts[0] = tweak[0] & 0xFFFFFFFFL;
parts[1] = (tweak[0] >>> 32) & 0xFFFFFFFFL;
parts[2] = tweak[1] & 0xFFFFFFFFL;
long carry = advance;
for (int i = 0; i < parts.length; i++)
{
carry += parts[i];
parts[i] = carry;
carry >>>= 32;
}
tweak[0] = ((parts[1] & 0xFFFFFFFFL) << 32) | (parts[0] & 0xFFFFFFFFL);
tweak[1] = (tweak[1] & 0xFFFFFFFF00000000L) | (parts[2] & 0xFFFFFFFFL);
}
else
{
long position = tweak[0];
position += advance;
tweak[0] = position;
if (position > LOW_RANGE)
{
extendedPosition = true;
}
}
}
public long[] getWords()
{
return tweak;
}
public String toString()
{
return getType() + " first: " + isFirst() + ", final: " + isFinal();
}
}
/**
* The Unique Block Iteration chaining mode.
*/
// TODO: This might be better as methods...
private class UBI
{
private final UbiTweak tweak = new UbiTweak();
/**
* 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 long[] message;
public UBI(int blockSize)
{
currentBlock = new byte[blockSize];
message = new long[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.setType(type);
currentOffset = 0;
}
public void update(byte[] value, int offset, int len, long[] 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.setFirst(false);
currentOffset = 0;
}
int toCopy = Math.min((len - copied), currentBlock.length - currentOffset);
System.arraycopy(value, offset + copied, currentBlock, currentOffset, toCopy);
copied += toCopy;
currentOffset += toCopy;
tweak.advancePosition(toCopy);
}
}
private void processBlock(long[] output)
{
threefish.init(true, chain, tweak.getWords());
for (int i = 0; i < message.length; i++)
{
message[i] = ThreefishEngine.bytesToWord(currentBlock, i * 8);
}
threefish.processBlock(message, output);
for (int i = 0; i < output.length; i++)
{
output[i] ^= message[i];
}
}
public void doFinal(long[] output)
{
// Pad remainder of current block with zeroes
for (int i = currentOffset; i < currentBlock.length; i++)
{
currentBlock[i] = 0;
}
tweak.setFinal(true);
processBlock(output);
}
}
/**
* Underlying Threefish tweakable block cipher
*/
final ThreefishEngine threefish;
/**
* Size of the digest output, in bytes
*/
private final int outputSizeBytes;
/**
* The current chaining/state value
*/
long[] chain;
/**
* The initial state value
*/
private long[] 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 final UBI ubi;
/**
* Buffer for single byte update method
*/
private final byte[] singleByte = new byte[1];
/**
* Constructs a Skein engine.
*
* @param blockSizeBits the internal state size in bits - one of {@link #SKEIN_256}, {@link #SKEIN_512} or
* {@link #SKEIN_1024}.
* @param outputSizeBits 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 IllegalArgumentException("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(threefish.getBlockSize());
}
/**
* Creates a SkeinEngine as an exact copy of an existing instance.
*/
public SkeinEngine(SkeinEngine engine)
{
this(engine.getBlockSize() * 8, engine.getOutputSize() * 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];
}
System.arraycopy(data, 0, existing, 0, existing.length);
return existing;
}
public Memoable copy()
{
return new SkeinEngine(this);
}
public void reset(Memoable other)
{
SkeinEngine s = (SkeinEngine)other;
if ((getBlockSize() != s.getBlockSize()) || (outputSizeBytes != s.outputSizeBytes))
{
throw new IllegalArgumentException("Incompatible parameters in provided SkeinEngine.");
}
copyIn(s);
}
public int getOutputSize()
{
return outputSizeBytes;
}
public int getBlockSize()
{
return threefish.getBlockSize();
}
/**
* Initialises the Skein engine with the provided parameters. See {@link SkeinParameters} for
* details on the parameterisation of the Skein hash function.
*
* @param params the parameters to apply to this engine, or <code>null</code> to use no parameters.
*/
public void init(SkeinParameters params)
{
this.chain = null;
this.key = null;
this.preMessageParameters = null;
this.postMessageParameters = null;
if (params != null)
{
byte[] key = params.getKey();
if (key.length < 16)
{
throw new IllegalArgumentException("Skein key must be at least 128 bits.");
}
initParams(params.getParameters());
}
createInitialState();
// Initialise message block
ubiInit(PARAM_TYPE_MESSAGE);
}
private void initParams(Hashtable parameters)
{
Enumeration keys = parameters.keys();
final Vector pre = new Vector();
final Vector post = new Vector();
while (keys.hasMoreElements())
{
Integer type = (Integer)keys.nextElement();
byte[] value = (byte[])parameters.get(type);
if (type.intValue() == PARAM_TYPE_KEY)
{
this.key = value;
}
else if (type.intValue() < PARAM_TYPE_MESSAGE)
{
pre.addElement(new Parameter(type.intValue(), value));
}
else
{
post.addElement(new Parameter(type.intValue(), value));
}
}
preMessageParameters = new Parameter[pre.size()];
pre.copyInto(preMessageParameters);
sort(preMessageParameters);
postMessageParameters = new Parameter[post.size()];
post.copyInto(postMessageParameters);
sort(postMessageParameters);
}
private static void sort(Parameter[] params)
{
if (params == null)
{
return;
}
// Insertion sort, for Java 1.1 compatibility
for (int i = 1; i < params.length; i++)
{
Parameter param = params[i];
int hole = i;
while (hole > 0 && param.getType() < params[hole - 1].getType())
{
params[hole] = params[hole - 1];
hole = hole - 1;
}
params[hole] = param;
}
}
/**
* Calculate the initial (pre message block) chaining state.
*/
private void createInitialState()
{
long[] precalc = (long[])INITIAL_STATES.get(variantIdentifier(getBlockSize(), getOutputSize()));
if ((key == null) && (precalc != null))
{
// Precalculated UBI(CFG)
chain = Arrays.clone(precalc);
}
else
{
// Blank initial state
chain = new long[getBlockSize() / 8];
// Process key block
if (key != null)
{
ubiComplete(SkeinParameters.PARAM_TYPE_KEY, key);
}
// Process configuration block
ubiComplete(PARAM_TYPE_CONFIG, new Configuration(outputSizeBytes * 8).getBytes());
}
// Process additional pre-message parameters
if (preMessageParameters != null)
{
for (int i = 0; i < preMessageParameters.length; i++)
{
Parameter param = preMessageParameters[i];
ubiComplete(param.getType(), param.getValue());
}
}
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()
{
System.arraycopy(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 IllegalArgumentException("Skein engine is not initialised.");
}
}
public void update(byte in)
{
singleByte[0] = in;
update(singleByte, 0, 1);
}
public void update(byte[] in, int inOff, int len)
{
checkInitialised();
ubi.update(in, inOff, len, chain);
}
public int doFinal(byte[] out, int outOff)
{
checkInitialised();
if (out.length < (outOff + outputSizeBytes))
{
throw new DataLengthException("Output buffer is too short to hold output of " + outputSizeBytes + " bytes");
}
// 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.getType(), param.getValue());
}
}
// Perform the output transform
final int blockSize = getBlockSize();
final int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize);
for (int i = 0; i < blocksRequired; i++)
{
final int toWrite = Math.min(blockSize, outputSizeBytes - (i * blockSize));
output(i, out, outOff + (i * blockSize), toWrite);
}
reset();
return outputSizeBytes;
}
private void output(long outputSequence, byte[] out, 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
long[] outputWords = new long[chain.length];
ubiInit(PARAM_TYPE_OUTPUT);
this.ubi.update(currentBytes, 0, currentBytes.length, outputWords);
ubi.doFinal(outputWords);
final int wordsRequired = ((outputBytes + 8 - 1) / 8);
for (int i = 0; i < wordsRequired; i++)
{
int toWrite = Math.min(8, outputBytes - (i * 8));
if (toWrite == 8)
{
ThreefishEngine.wordToBytes(outputWords[i], out, outOff + (i * 8));
}
else
{
ThreefishEngine.wordToBytes(outputWords[i], currentBytes, 0);
System.arraycopy(currentBytes, 0, out, outOff + (i * 8), toWrite);
}
}
}
}