package org.bouncycastle.crypto.test.speedy; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.TweakableBlockCipherParameters; public class ThreefishReferenceEngine implements BlockCipher { /** * The tweak input is always 128 bits */ private static final int TWEAK_SIZE = 16; private static long C_240 = 0x1BD11BDAA9FC1A22L; private final int blocksize = 64; private final int rounds = 72; private final int words = 8; private boolean forEncryption; private long[] block = new long[words]; private int[][] rotations = R8; /** * Rotation constants Rd,j for Nw = 8. */ private static final int[][] R8 = { {46, 36, 19, 37}, {33, 27, 14, 42}, {17, 49, 36, 39}, {44, 9, 54, 56}, {39, 30, 34, 24}, {13, 50, 10, 17}, {25, 29, 39, 43}, {8, 35, 56, 22}}; private long[] t; private long kw[]; public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { if (params instanceof TweakableBlockCipherParameters) { init(forEncryption, (TweakableBlockCipherParameters)params); } else if (params instanceof KeyParameter) { init(forEncryption, new TweakableBlockCipherParameters((KeyParameter)params, new byte[TWEAK_SIZE])); } else { throw new IllegalArgumentException("Invalid parameter passed to Threefish init - " + params.getClass().getName()); } } public void init(boolean forEncryption, TweakableBlockCipherParameters params) throws IllegalArgumentException { // TODO: Remove some of the NPEs that can be avoided in the Params // classes if ((params.getKey() == null) || (params.getKey().getKey() == null) || (params.getKey().getKey().length != blocksize)) { throw new IllegalArgumentException("Threefish key must be same size as block (%d bytes)" + blocksize); } if ((params.getTweak() == null) || (params.getTweak().length != TWEAK_SIZE)) { throw new IllegalArgumentException("Threefish tweak must be %d bytes" + TWEAK_SIZE); } this.forEncryption = forEncryption; generateKeySchedule(params.getKey().getKey(), params.getTweak()); } private void generateKeySchedule(byte[] key, byte[] tweak) { // TODO: This key schedule can/should be generated incrementally/on demand during encrypt/decrypt // to reduce memory overhead (currently 1.2MB = (rounds/4+1)=19 * words=8 * 8 bytes/word) t = new long[3]; t[0] = BytesToWord(tweak, 0); t[1] = BytesToWord(tweak, 8); t[2] = t[0] ^ t[1]; kw = new long[words + 1]; long knw = C_240; for (int i = 0; i < words; i++) { kw[i] = BytesToWord(key, i * 8); knw = knw ^ kw[i]; } kw[kw.length - 1] = knw; } private static long BytesToWord(byte[] bytes, int off) { long word = 0; int index = off; word = (bytes[index++] & 0xffL); word |= (bytes[index++] & 0xffL) << 8; word |= (bytes[index++] & 0xffL) << 16; word |= (bytes[index++] & 0xffL) << 24; word |= (bytes[index++] & 0xffL) << 32; word |= (bytes[index++] & 0xffL) << 40; word |= (bytes[index++] & 0xffL) << 48; word |= (bytes[index++] & 0xffL) << 56; return word; } private static void WordToBytes(long word, byte[] bytes, int off) { int index = off; bytes[index++] = (byte)word; bytes[index++] = (byte)(word >> 8); bytes[index++] = (byte)(word >> 16); bytes[index++] = (byte)(word >> 24); bytes[index++] = (byte)(word >> 32); bytes[index++] = (byte)(word >> 40); bytes[index++] = (byte)(word >> 48); bytes[index++] = (byte)(word >> 56); } public String getAlgorithmName() { return "Threefish"; } public int getBlockSize() { return blocksize; } public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException { // TODO: Check init state if (kw == null) { throw new IllegalStateException("Threefish engine not initialised"); } if ((inOff + blocksize) > in.length) { throw new DataLengthException("Input buffer too short"); } if ((outOff + blocksize) > out.length) { throw new DataLengthException("Output buffer too short"); } if (forEncryption) { unpackBlock(in, inOff); encryptBlock(); packBlock(out, outOff); } else { unpackBlock(in, inOff); decryptBlock(); packBlock(out, outOff); } return blocksize; } private void decryptBlock() { for (int d = rounds; d > 0; d--) { // Add subkey every 4 rounds if ((d % 4) == 0) { uninjectSubkey(d / 4); } // Permute unpermute(); // Mix for (int j = 0; j < words / 2; j++) { unmix(j, d - 1); } } // Remove first subkey uninjectSubkey(0); } private void injectSubkey(int s) { for (int i = 0; i < (words - 3); i++) { block[i] += kw[(s + i) % (words + 1)]; } block[words - 3] += kw[(s + words - 3) % (words + 1)] + t[s % 3]; block[words - 2] += kw[(s + words - 2) % (words + 1)] + t[(s + 1) % 3]; block[words - 1] += kw[(s + words - 1) % (words + 1)] + s; } private void uninjectSubkey(int s) { for (int i = 0; i < (words - 3); i++) { block[i] -= kw[(s + i) % (words + 1)]; } block[words - 3] -= kw[(s + words - 3) % (words + 1)] + t[s % 3]; block[words - 2] -= kw[(s + words - 2) % (words + 1)] + t[(s + 1) % 3]; block[words - 1] -= kw[(s + words - 1) % (words + 1)] + s; } private void encryptBlock() { for (int d = 0; d < rounds; d++) { // Add subkey every 4 rounds if ((d % 4) == 0) { injectSubkey(d / 4); } // Mix for (int j = 0; j < words / 2; j++) { mix(j, d); } // Permute permute(); } // Final key addition injectSubkey(rounds / 4); } private void permute() { // Permute in place for Nw = 8 long f0 = block[0]; long f3 = block[3]; block[0] = block[2]; block[1] = block[1]; block[2] = block[4]; block[3] = block[7]; block[4] = block[6]; block[5] = block[5]; block[6] = f0; block[7] = f3; } private void unpermute() { // TODO: Change these to tables // Permute in place for Nw = 8 long f6 = block[6]; long f7 = block[7]; block[7] = block[3]; block[6] = block[4]; block[5] = block[5]; block[4] = block[2]; block[3] = f7; block[2] = block[0]; block[1] = block[1]; block[0] = f6; } private void mix(int j, int d) { // ed,2j and ed,2j+1 int b0 = 2 * j; int b1 = b0 + 1; // y0 = x0 + x1 block[b0] = block[b0] + block[b1]; // y1 = (x1 <<< R(d mod 8,j)) xor y0 block[b1] = Long.rotateLeft(block[b1], rotations[d % 8][j]) ^ block[b0]; } private void unmix(int j, int d) { // ed,2j and ed,2j+1 int b0 = 2 * j; int b1 = b0 + 1; // x1 = (y1 ^ y0) >>> R(d mod 8, j)) block[b1] = Long.rotateRight(block[b1] ^ block[b0], rotations[d % 8][j]); // x0 = y0 - x1 block[b0] = block[b0] - block[b1]; } public static void main(String[] args) { ThreefishReferenceEngine engine = new ThreefishReferenceEngine(); engine.fu(); } private void fu() { block[0] = 0x12; block[1] = 0x34; block[2] = 0x56; block[3] = 0x78; block[4] = 0x90; block[5] = 0xAB; block[6] = 0xCD; block[7] = 0xEF; for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } mix(0, 4); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } unmix(0, 4); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } permute(); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } unpermute(); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } generateKeySchedule(new byte[blocksize], new byte[TWEAK_SIZE]); injectSubkey(5); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } uninjectSubkey(5); System.err.println("========="); for (int i = 0; i < block.length; i++) { System.err.println(i + " : " + Long.toHexString(block[i])); } } private void packBlock(byte[] out, int outOff) { for (int i = 0; i < block.length; i++) { WordToBytes(block[i], out, outOff + (i * 8)); } } private long[] unpackBlock(byte[] bytes, int index) { for (int i = 0; i < block.length; i++) { block[i] = BytesToWord(bytes, index + (i * 8)); } return block; } public void reset() { } }