All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.crypto.digests.SkeinEngine Maven / Gradle / Ivy

There is a newer version: 1.70_1
Show newest version
package org.bouncycastle.crypto.digests;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.bouncycastle.crypto.OutputLengthException;
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.Integers;
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.
 * 

* 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 {@link SkeinDigest} and {@link SkeinMac}, implementing the * parameter based configuration system that allows Skein to be adapted to multiple applications.
* Initialising the engine with {@link SkeinParameters} allows standard and arbitrary parameters to * be applied during the Skein hash function. *

* Implemented: *

    *
  • 256, 512 and 1024 bit internal states.
  • *
  • Full 96 bit input length.
  • *
  • Parameters defined in the Skein specification, and arbitrary other pre and post message * parameters.
  • *
  • Arbitrary output size in 1 byte intervals.
  • *
*

* Not implemented: *

    *
  • Sub-byte length input (bit padding).
  • *
  • Tree hashing.
  • *
* * @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 Integers.valueOf((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 null 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 OutputLengthException("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.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); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy