blob: 4a6be46b4c95252ead44ec1840ad0178179bc199 [file] [log] [blame]
package org.bouncycastle.crypto.digests;
import org.bouncycastle.crypto.ExtendedDigest;
import org.bouncycastle.util.Arrays;
/**
* implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
* <p>
* Following the naming conventions used in the C source code to enable easy review of the implementation.
*/
public class KeccakDigest
implements ExtendedDigest
{
private static long[] KeccakRoundConstants = keccakInitializeRoundConstants();
private static int[] KeccakRhoOffsets = keccakInitializeRhoOffsets();
private static long[] keccakInitializeRoundConstants()
{
long[] keccakRoundConstants = new long[24];
byte[] LFSRstate = new byte[1];
LFSRstate[0] = 0x01;
int i, j, bitPosition;
for (i = 0; i < 24; i++)
{
keccakRoundConstants[i] = 0;
for (j = 0; j < 7; j++)
{
bitPosition = (1 << j) - 1;
if (LFSR86540(LFSRstate))
{
keccakRoundConstants[i] ^= 1L << bitPosition;
}
}
}
return keccakRoundConstants;
}
private static boolean LFSR86540(byte[] LFSR)
{
boolean result = (((LFSR[0]) & 0x01) != 0);
if (((LFSR[0]) & 0x80) != 0)
{
LFSR[0] = (byte)(((LFSR[0]) << 1) ^ 0x71);
}
else
{
LFSR[0] <<= 1;
}
return result;
}
private static int[] keccakInitializeRhoOffsets()
{
int[] keccakRhoOffsets = new int[25];
int x, y, t, newX, newY;
keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0;
x = 1;
y = 0;
for (t = 0; t < 24; t++)
{
keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64;
newX = (0 * x + 1 * y) % 5;
newY = (2 * x + 3 * y) % 5;
x = newX;
y = newY;
}
return keccakRhoOffsets;
}
protected byte[] state = new byte[(1600 / 8)];
protected byte[] dataQueue = new byte[(1536 / 8)];
protected int rate;
protected int bitsInQueue;
protected int fixedOutputLength;
protected boolean squeezing;
protected int bitsAvailableForSqueezing;
protected byte[] chunk;
protected byte[] oneByte;
private void clearDataQueueSection(int off, int len)
{
for (int i = off; i != off + len; i++)
{
dataQueue[i] = 0;
}
}
public KeccakDigest()
{
this(288);
}
public KeccakDigest(int bitLength)
{
init(bitLength);
}
public KeccakDigest(KeccakDigest source) {
System.arraycopy(source.state, 0, this.state, 0, source.state.length);
System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length);
this.rate = source.rate;
this.bitsInQueue = source.bitsInQueue;
this.fixedOutputLength = source.fixedOutputLength;
this.squeezing = source.squeezing;
this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing;
this.chunk = Arrays.clone(source.chunk);
this.oneByte = Arrays.clone(source.oneByte);
}
public String getAlgorithmName()
{
return "Keccak-" + fixedOutputLength;
}
public int getDigestSize()
{
return fixedOutputLength / 8;
}
public void update(byte in)
{
oneByte[0] = in;
absorb(oneByte, 0, 8L);
}
public void update(byte[] in, int inOff, int len)
{
absorb(in, inOff, len * 8L);
}
public int doFinal(byte[] out, int outOff)
{
squeeze(out, outOff, fixedOutputLength);
reset();
return getDigestSize();
}
/*
* TODO Possible API change to support partial-byte suffixes.
*/
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
{
if (partialBits > 0)
{
oneByte[0] = partialByte;
absorb(oneByte, 0, partialBits);
}
squeeze(out, outOff, fixedOutputLength);
reset();
return getDigestSize();
}
public void reset()
{
init(fixedOutputLength);
}
/**
* Return the size of block that the compression function is applied to in bytes.
*
* @return internal byte length of a block.
*/
public int getByteLength()
{
return rate / 8;
}
private void init(int bitLength)
{
switch (bitLength)
{
case 288:
initSponge(1024, 576);
break;
case 128:
initSponge(1344, 256);
break;
case 224:
initSponge(1152, 448);
break;
case 256:
initSponge(1088, 512);
break;
case 384:
initSponge(832, 768);
break;
case 512:
initSponge(576, 1024);
break;
default:
throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.");
}
}
private void initSponge(int rate, int capacity)
{
if (rate + capacity != 1600)
{
throw new IllegalStateException("rate + capacity != 1600");
}
if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))
{
throw new IllegalStateException("invalid rate value");
}
this.rate = rate;
// this is never read, need to check to see why we want to save it
// this.capacity = capacity;
Arrays.fill(this.state, (byte)0);
Arrays.fill(this.dataQueue, (byte)0);
this.bitsInQueue = 0;
this.squeezing = false;
this.bitsAvailableForSqueezing = 0;
this.fixedOutputLength = capacity / 2;
this.chunk = new byte[rate / 8];
this.oneByte = new byte[1];
}
private void absorbQueue()
{
KeccakAbsorb(state, dataQueue, rate / 8);
bitsInQueue = 0;
}
protected void absorb(byte[] data, int off, long databitlen)
{
long i, j, wholeBlocks;
if ((bitsInQueue % 8) != 0)
{
throw new IllegalStateException("attempt to absorb with odd length queue");
}
if (squeezing)
{
throw new IllegalStateException("attempt to absorb while squeezing");
}
i = 0;
while (i < databitlen)
{
if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate)))
{
wholeBlocks = (databitlen - i) / rate;
for (j = 0; j < wholeBlocks; j++)
{
System.arraycopy(data, (int)(off + (i / 8) + (j * chunk.length)), chunk, 0, chunk.length);
// displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8);
KeccakAbsorb(state, chunk, chunk.length);
}
i += wholeBlocks * rate;
}
else
{
int partialBlock = (int)(databitlen - i);
if (partialBlock + bitsInQueue > rate)
{
partialBlock = rate - bitsInQueue;
}
int partialByte = partialBlock % 8;
partialBlock -= partialByte;
System.arraycopy(data, off + (int)(i / 8), dataQueue, bitsInQueue / 8, partialBlock / 8);
bitsInQueue += partialBlock;
i += partialBlock;
if (bitsInQueue == rate)
{
absorbQueue();
}
if (partialByte > 0)
{
int mask = (1 << partialByte) - 1;
dataQueue[bitsInQueue / 8] = (byte)(data[off + ((int)(i / 8))] & mask);
bitsInQueue += partialByte;
i += partialByte;
}
}
}
}
private void padAndSwitchToSqueezingPhase()
{
if (bitsInQueue + 1 == rate)
{
dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
absorbQueue();
clearDataQueueSection(0, rate / 8);
}
else
{
clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8);
dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
}
dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8);
absorbQueue();
// displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---");
if (rate == 1024)
{
KeccakExtract1024bits(state, dataQueue);
bitsAvailableForSqueezing = 1024;
}
else
{
KeccakExtract(state, dataQueue, rate / 64);
bitsAvailableForSqueezing = rate;
}
// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
squeezing = true;
}
protected void squeeze(byte[] output, int offset, long outputLength)
{
long i;
int partialBlock;
if (!squeezing)
{
padAndSwitchToSqueezingPhase();
}
if ((outputLength % 8) != 0)
{
throw new IllegalStateException("outputLength not a multiple of 8");
}
i = 0;
while (i < outputLength)
{
if (bitsAvailableForSqueezing == 0)
{
keccakPermutation(state);
if (rate == 1024)
{
KeccakExtract1024bits(state, dataQueue);
bitsAvailableForSqueezing = 1024;
}
else
{
KeccakExtract(state, dataQueue, rate / 64);
bitsAvailableForSqueezing = rate;
}
// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
}
partialBlock = bitsAvailableForSqueezing;
if ((long)partialBlock > outputLength - i)
{
partialBlock = (int)(outputLength - i);
}
System.arraycopy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int)(i / 8), partialBlock / 8);
bitsAvailableForSqueezing -= partialBlock;
i += partialBlock;
}
}
private void fromBytesToWords(long[] stateAsWords, byte[] state)
{
for (int i = 0; i < (1600 / 64); i++)
{
stateAsWords[i] = 0;
int index = i * (64 / 8);
for (int j = 0; j < (64 / 8); j++)
{
stateAsWords[i] |= ((long)state[index + j] & 0xff) << ((8 * j));
}
}
}
private void fromWordsToBytes(byte[] state, long[] stateAsWords)
{
for (int i = 0; i < (1600 / 64); i++)
{
int index = i * (64 / 8);
for (int j = 0; j < (64 / 8); j++)
{
state[index + j] = (byte)((stateAsWords[i] >>> ((8 * j))) & 0xFF);
}
}
}
private void keccakPermutation(byte[] state)
{
long[] longState = new long[state.length / 8];
fromBytesToWords(longState, state);
// displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState);
keccakPermutationOnWords(longState);
// displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState);
fromWordsToBytes(state, longState);
}
private void keccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes)
{
int i;
for (i = 0; i < dataLengthInBytes; i++)
{
state[i] ^= data[i];
}
keccakPermutation(state);
}
private void keccakPermutationOnWords(long[] state)
{
int i;
// displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state);
for (i = 0; i < 24; i++)
{
// displayIntermediateValues.displayRoundNumber(3, i);
theta(state);
// displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state);
rho(state);
// displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state);
pi(state);
// displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state);
chi(state);
// displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state);
iota(state, i);
// displayIntermediateValues.displayStateAs64bitWords(3, "After iota", state);
}
}
long[] C = new long[5];
private void theta(long[] A)
{
for (int x = 0; x < 5; x++)
{
C[x] = 0;
for (int y = 0; y < 5; y++)
{
C[x] ^= A[x + 5 * y];
}
}
for (int x = 0; x < 5; x++)
{
long dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5];
for (int y = 0; y < 5; y++)
{
A[x + 5 * y] ^= dX;
}
}
}
private void rho(long[] A)
{
for (int x = 0; x < 5; x++)
{
for (int y = 0; y < 5; y++)
{
int index = x + 5 * y;
A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]);
}
}
}
long[] tempA = new long[25];
private void pi(long[] A)
{
System.arraycopy(A, 0, tempA, 0, tempA.length);
for (int x = 0; x < 5; x++)
{
for (int y = 0; y < 5; y++)
{
A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y];
}
}
}
long[] chiC = new long[5];
private void chi(long[] A)
{
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]);
}
for (int x = 0; x < 5; x++)
{
A[x + 5 * y] = chiC[x];
}
}
}
private void iota(long[] A, int indexRound)
{
A[(((0) % 5) + 5 * ((0) % 5))] ^= KeccakRoundConstants[indexRound];
}
private void KeccakAbsorb(byte[] byteState, byte[] data, int dataInBytes)
{
keccakPermutationAfterXor(byteState, data, dataInBytes);
}
private void KeccakExtract1024bits(byte[] byteState, byte[] data)
{
System.arraycopy(byteState, 0, data, 0, 128);
}
private void KeccakExtract(byte[] byteState, byte[] data, int laneCount)
{
System.arraycopy(byteState, 0, data, 0, laneCount * 8);
}
}