bouncycastle.crypto.digests.SHA3Digest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of woodlouse Show documentation
Show all versions of woodlouse Show documentation
Lightweight crypto toolkit for Android and Java 6+
The newest version!
package bouncycastle.crypto.digests;
import bouncycastle.crypto.ExtendedDigest;
import bouncycastle.util.Arrays;
/**
* implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
*
* Following the naming conventions used in the C source code to enable easy review of the implementation.
*/
public class SHA3Digest
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;
}
private byte[] state = new byte[(1600 / 8)];
private byte[] dataQueue = new byte[(1536 / 8)];
private int rate;
private int bitsInQueue;
private int fixedOutputLength;
private boolean squeezing;
private int bitsAvailableForSqueezing;
private byte[] chunk;
private byte[] oneByte;
private void clearDataQueueSection(int off, int len)
{
for (int i = off; i != off + len; i++)
{
dataQueue[i] = 0;
}
}
public SHA3Digest()
{
init(0);
}
public SHA3Digest(int bitLength)
{
init(bitLength);
}
public SHA3Digest(SHA3Digest 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 "SHA3-" + fixedOutputLength;
}
public int getDigestSize()
{
return fixedOutputLength / 8;
}
public void update(byte in)
{
oneByte[0] = in;
doUpdate(oneByte, 0, 8L);
}
public void update(byte[] in, int inOff, int len)
{
doUpdate(in, inOff, len * 8L);
}
public int doFinal(byte[] out, int outOff)
{
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 0:
case 288:
initSponge(1024, 576);
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 224, 256, 384, or 512.");
}
}
private void doUpdate(byte[] data, int off, long databitlen)
{
if ((databitlen % 8) == 0)
{
absorb(data, off, databitlen);
}
else
{
absorb(data, off, databitlen - (databitlen % 8));
byte[] lastByte = new byte[1];
lastByte[0] = (byte)(data[off + (int)(databitlen / 8)] >> (8 - (databitlen % 8)));
absorb(lastByte, off, databitlen % 8);
}
}
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;
this.fixedOutputLength = 0;
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;
}
private 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;
}
private 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);
}
}