diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2015-09-06 16:37:29 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2015-09-06 16:37:29 +0700 |
commit | d364d3fcd41af5e71e234dacef8aac5064fbc010 (patch) | |
tree | 2008007bdbd41e63d59a104db07a3d7471392962 /crypto/src | |
parent | Add Miller-Rabin methods (diff) | |
download | BouncyCastle.NET-ed25519-d364d3fcd41af5e71e234dacef8aac5064fbc010.tar.xz |
Port of Keccak, SHA-3, SHAKE from Java
Diffstat (limited to 'crypto/src')
-rw-r--r-- | crypto/src/crypto/IXof.cs | 22 | ||||
-rw-r--r-- | crypto/src/crypto/digests/KeccakDigest.cs | 534 | ||||
-rw-r--r-- | crypto/src/crypto/digests/SHA3Digest.cs | 538 | ||||
-rw-r--r-- | crypto/src/crypto/digests/ShakeDigest.cs | 111 |
4 files changed, 699 insertions, 506 deletions
diff --git a/crypto/src/crypto/IXof.cs b/crypto/src/crypto/IXof.cs new file mode 100644 index 000000000..e9e2253a0 --- /dev/null +++ b/crypto/src/crypto/IXof.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// <remarks> + /// With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + /// This interface provides the extra method required to support variable output on a digest implementation. + /// </remarks> + public interface IXof + : IDigest + { + /** + * Output the results of the final calculation for this digest to outLen number of bytes. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + * @return the number of bytes written + */ + int DoFinal(byte[] output, int outOff, int outLen); + } +} diff --git a/crypto/src/crypto/digests/KeccakDigest.cs b/crypto/src/crypto/digests/KeccakDigest.cs new file mode 100644 index 000000000..2d6cf393c --- /dev/null +++ b/crypto/src/crypto/digests/KeccakDigest.cs @@ -0,0 +1,534 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// <summary> + /// Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// </summary> + /// <remarks> + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// </remarks> + public class KeccakDigest + : IDigest, IMemoable + { + private static readonly ulong[] KeccakRoundConstants = KeccakInitializeRoundConstants(); + + private static readonly int[] KeccakRhoOffsets = KeccakInitializeRhoOffsets(); + + private static ulong[] KeccakInitializeRoundConstants() + { + ulong[] keccakRoundConstants = new ulong[24]; + byte LFSRState = 0x01; + + for (int i = 0; i < 24; i++) + { + keccakRoundConstants[i] = 0; + for (int j = 0; j < 7; j++) + { + int bitPosition = (1 << j) - 1; + + // LFSR86540 + + bool loBit = (LFSRState & 0x01) != 0; + if (loBit) + { + keccakRoundConstants[i] ^= 1UL << bitPosition; + } + + bool hiBit = (LFSRState & 0x80) != 0; + LFSRState <<= 1; + if (hiBit) + { + LFSRState ^= 0x71; + } + + } + } + + return keccakRoundConstants; + } + + private static int[] KeccakInitializeRhoOffsets() + { + int[] keccakRhoOffsets = new int[25]; + int x, y, t, newX, newY; + + int rhoOffset = 0; + keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = rhoOffset; + x = 1; + y = 0; + for (t = 1; t < 25; t++) + { + //rhoOffset = ((t + 1) * (t + 2) / 2) % 64; + rhoOffset = (rhoOffset + t) & 63; + keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = rhoOffset; + 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 bool 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) + { + CopyIn(source); + } + + private void CopyIn(KeccakDigest source) + { + Array.Copy(source.state, 0, this.state, 0, source.state.Length); + Array.Copy(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 virtual string AlgorithmName + { + get { return "Keccak-" + fixedOutputLength; } + } + + public virtual int GetDigestSize() + { + return fixedOutputLength / 8; + } + + public virtual void Update(byte input) + { + oneByte[0] = input; + + Absorb(oneByte, 0, 8L); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + Absorb(input, inOff, len * 8L); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + Squeeze(output, outOff, fixedOutputLength); + + Reset(); + + return GetDigestSize(); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + if (partialBits > 0) + { + oneByte[0] = partialByte; + Absorb(oneByte, 0, partialBits); + } + + Squeeze(output, outOff, fixedOutputLength); + + Reset(); + + return GetDigestSize(); + } + + public virtual 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 virtual int GetByteLength() + { + return rate / 8; + } + + private void Init(int bitLength) + { + switch (bitLength) + { + case 128: + InitSponge(1344, 256); + break; + case 224: + InitSponge(1152, 448); + break; + case 256: + InitSponge(1088, 512); + break; + case 288: + InitSponge(1024, 576); + break; + case 384: + InitSponge(832, 768); + break; + case 512: + InitSponge(576, 1024); + break; + default: + throw new ArgumentException("must be one of 128, 224, 256, 288, 384, or 512.", "bitLength"); + } + } + + private void InitSponge(int rate, int capacity) + { + if (rate + capacity != 1600) + { + throw new InvalidOperationException("rate + capacity != 1600"); + } + if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) + { + throw new InvalidOperationException("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; + } + + protected virtual void Absorb(byte[] data, int off, long databitlen) + { + long i, j, wholeBlocks; + + if ((bitsInQueue % 8) != 0) + { + throw new InvalidOperationException("attempt to absorb with odd length queue."); + } + if (squeezing) + { + throw new InvalidOperationException("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++) + { + Array.Copy(data, (int)(off + (i / 8) + (j * chunk.Length)), chunk, 0, chunk.Length); + + 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; + Array.Copy(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] |= (byte)(1U << (bitsInQueue % 8)); + AbsorbQueue(); + ClearDataQueueSection(0, rate / 8); + } + else + { + ClearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8); + dataQueue[bitsInQueue / 8] |= (byte)(1U << (bitsInQueue % 8)); + } + dataQueue[(rate - 1) / 8] |= (byte)(1U << ((rate - 1) % 8)); + AbsorbQueue(); + + if (rate == 1024) + { + KeccakExtract1024bits(state, dataQueue); + bitsAvailableForSqueezing = 1024; + } + else + { + KeccakExtract(state, dataQueue, rate / 64); + bitsAvailableForSqueezing = rate; + } + + squeezing = true; + } + + protected virtual void Squeeze(byte[] output, int offset, long outputLength) + { + long i; + int partialBlock; + + if (!squeezing) + { + PadAndSwitchToSqueezingPhase(); + } + if ((outputLength % 8) != 0) + { + throw new InvalidOperationException("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; + } + } + partialBlock = bitsAvailableForSqueezing; + if ((long)partialBlock > outputLength - i) + { + partialBlock = (int)(outputLength - i); + } + + Array.Copy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int)(i / 8), partialBlock / 8); + bitsAvailableForSqueezing -= partialBlock; + i += partialBlock; + } + } + + private static void FromBytesToWords(ulong[] 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] |= ((ulong)state[index + j] & 0xff) << ((8 * j)); + } + } + } + + private static void FromWordsToBytes(byte[] state, ulong[] 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)); + } + } + } + + private void KeccakPermutation(byte[] state) + { + ulong[] longState = new ulong[state.Length / 8]; + + FromBytesToWords(longState, state); + + KeccakPermutationOnWords(longState); + + FromWordsToBytes(state, longState); + } + + private void KeccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes) + { + for (int i = 0; i < dataLengthInBytes; i++) + { + state[i] ^= data[i]; + } + + KeccakPermutation(state); + } + + private void KeccakPermutationOnWords(ulong[] state) + { + int i; + + for (i = 0; i < 24; i++) + { + Theta(state); + Rho(state); + Pi(state); + Chi(state); + Iota(state, i); + } + } + + ulong[] C = new ulong[5]; + + private void Theta(ulong[] 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++) + { + ulong 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(ulong[] 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]); + } + } + } + + ulong[] tempA = new ulong[25]; + + private void Pi(ulong[] A) + { + Array.Copy(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]; + } + } + } + + ulong[] chiC = new ulong[5]; + + private void Chi(ulong[] 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 static void Iota(ulong[] 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) + { + Array.Copy(byteState, 0, data, 0, 128); + } + + private void KeccakExtract(byte[] byteState, byte[] data, int laneCount) + { + Array.Copy(byteState, 0, data, 0, laneCount * 8); + } + + public virtual IMemoable Copy() + { + return new KeccakDigest(this); + } + + public virtual void Reset(IMemoable other) + { + KeccakDigest d = (KeccakDigest)other; + + CopyIn(d); + } + } +} diff --git a/crypto/src/crypto/digests/SHA3Digest.cs b/crypto/src/crypto/digests/SHA3Digest.cs index 2c6837b3c..890b665cf 100644 --- a/crypto/src/crypto/digests/SHA3Digest.cs +++ b/crypto/src/crypto/digests/SHA3Digest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Org.BouncyCastle.Utilities; @@ -11,550 +12,75 @@ namespace Org.BouncyCastle.Crypto.Digests /// Following the naming conventions used in the C source code to enable easy review of the implementation. /// </remarks> public class Sha3Digest - : IDigest, IMemoable + : KeccakDigest { - private static readonly ulong[] KeccakRoundConstants = KeccakInitializeRoundConstants(); - - private static readonly int[] KeccakRhoOffsets = KeccakInitializeRhoOffsets(); - - private static ulong[] KeccakInitializeRoundConstants() - { - ulong[] keccakRoundConstants = new ulong[24]; - byte LFSRState = 0x01; - - for (int i = 0; i < 24; i++) - { - keccakRoundConstants[i] = 0; - for (int j = 0; j < 7; j++) - { - int bitPosition = (1 << j) - 1; - - // LFSR86540 - - bool loBit = (LFSRState & 0x01) != 0; - if (loBit) - { - keccakRoundConstants[i] ^= 1UL << bitPosition; - } - - bool hiBit = (LFSRState & 0x80) != 0; - LFSRState <<= 1; - if (hiBit) - { - LFSRState ^= 0x71; - } - - } - } - - return keccakRoundConstants; - } - - private static int[] KeccakInitializeRhoOffsets() - { - int[] keccakRhoOffsets = new int[25]; - int x, y, t, newX, newY; - - int rhoOffset = 0; - keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = rhoOffset; - x = 1; - y = 0; - for (t = 1; t < 25; t++) - { - //rhoOffset = ((t + 1) * (t + 2) / 2) % 64; - rhoOffset = (rhoOffset + t) & 63; - keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = rhoOffset; - 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 bool 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) - { - CopyIn(source); - } - - private void CopyIn(Sha3Digest source) - { - Array.Copy(source.state, 0, this.state, 0, source.state.Length); - Array.Copy(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 virtual string AlgorithmName - { - get { return "SHA3-" + fixedOutputLength; } - } - - public virtual int GetDigestSize() - { - return fixedOutputLength / 8; - } - - public virtual void Update(byte input) - { - oneByte[0] = input; - - DoUpdate(oneByte, 0, 8L); - } - - public virtual void BlockUpdate(byte[] input, int inOff, int len) - { - DoUpdate(input, inOff, len * 8L); - } - - public virtual int DoFinal(byte[] output, int outOff) - { - Squeeze(output, outOff, fixedOutputLength); - - Reset(); - - return GetDigestSize(); - } - - public virtual 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 virtual int GetByteLength() - { - return rate / 8; - } - - private void Init(int bitLength) + private static int CheckBitLength(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; + return bitLength; default: - throw new ArgumentException("must be one of 224, 256, 384, or 512.", "bitLength"); - } - } - - 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)] >> (int)(8 - (databitlen % 8))); - Absorb(lastByte, off, databitlen % 8); - } - } - - private void InitSponge(int rate, int capacity) - { - if (rate + capacity != 1600) - { - throw new InvalidOperationException("rate + capacity != 1600"); - } - if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) - { - throw new InvalidOperationException("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 InvalidOperationException("attempt to absorb with odd length queue."); - } - if (squeezing) - { - throw new InvalidOperationException("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++) - { - Array.Copy(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; - Array.Copy(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; - } - } + throw new ArgumentException(bitLength + " not supported for SHA-3", "bitLength"); } } - private void PadAndSwitchToSqueezingPhase() - { - if (bitsInQueue + 1 == rate) - { - dataQueue[bitsInQueue / 8] |= (byte)(1U << (bitsInQueue % 8)); - AbsorbQueue(); - ClearDataQueueSection(0, rate / 8); - } - else - { - ClearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8); - dataQueue[bitsInQueue / 8] |= (byte)(1U << (bitsInQueue % 8)); - } - dataQueue[(rate - 1) / 8] |= (byte)(1U << ((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 InvalidOperationException("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); - } - - Array.Copy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int)(i / 8), partialBlock / 8); - bitsAvailableForSqueezing -= partialBlock; - i += partialBlock; - } - } - - private static void FromBytesToWords(ulong[] 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] |= ((ulong)state[index + j] & 0xff) << ((8 * j)); - } - } - } - - private static void FromWordsToBytes(byte[] state, ulong[] stateAsWords) + public Sha3Digest() + : this(256) { - 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)); - } - } } - private void KeccakPermutation(byte[] state) + public Sha3Digest(int bitLength) + : base(CheckBitLength(bitLength)) { - ulong[] longState = new ulong[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) + public Sha3Digest(Sha3Digest source) + : base(source) { - for (int i = 0; i < dataLengthInBytes; i++) - { - state[i] ^= data[i]; - } - - KeccakPermutation(state); } - private void KeccakPermutationOnWords(ulong[] state) + public override string AlgorithmName { - 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); - } + get { return "SHA3-" + fixedOutputLength; } } - ulong[] C = new ulong[5]; - - private void Theta(ulong[] A) + public override int DoFinal(byte[] output, int outOff) { - 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++) - { - ulong 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; - } - } - } + Absorb(new byte[]{ 0x02 }, 0, 2); - private void Rho(ulong[] 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]); - } - } + return base.DoFinal(output, outOff); } - ulong[] tempA = new ulong[25]; - - private void Pi(ulong[] A) + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) { - Array.Copy(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]; - } - } - } + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); - ulong[] chiC = new ulong[5]; + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 2; - private void Chi(ulong[] A) - { - for (int y = 0; y < 5; y++) + if (finalBits >= 8) { - 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]; - } + oneByte[0] = (byte)finalInput; + Absorb(oneByte, 0, 8); + finalBits -= 8; + finalInput >>= 8; } - } - - private static void Iota(ulong[] 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) - { - Array.Copy(byteState, 0, data, 0, 128); - } - private void KeccakExtract(byte[] byteState, byte[] data, int laneCount) - { - Array.Copy(byteState, 0, data, 0, laneCount * 8); + return base.DoFinal(output, outOff, (byte)finalInput, finalBits); } - public IMemoable Copy() + public override IMemoable Copy() { return new Sha3Digest(this); } - - public void Reset(IMemoable other) - { - Sha3Digest d = (Sha3Digest)other; - - CopyIn(d); - } - - } } diff --git a/crypto/src/crypto/digests/ShakeDigest.cs b/crypto/src/crypto/digests/ShakeDigest.cs new file mode 100644 index 000000000..fd7d85681 --- /dev/null +++ b/crypto/src/crypto/digests/ShakeDigest.cs @@ -0,0 +1,111 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// <summary> + /// Implementation of SHAKE based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// </summary> + /// <remarks> + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// </remarks> + public class ShakeDigest + : KeccakDigest, IXof + { + private static int CheckBitLength(int bitLength) + { + switch (bitLength) + { + case 128: + case 256: + return bitLength; + default: + throw new ArgumentException(bitLength + " not supported for SHAKE", "bitLength"); + } + } + + public ShakeDigest() + : this(128) + { + } + + public ShakeDigest(int bitLength) + : base(CheckBitLength(bitLength)) + { + } + + public ShakeDigest(ShakeDigest source) + : base(source) + { + } + + public override string AlgorithmName + { + get { return "SHAKE" + fixedOutputLength; } + } + + public override int DoFinal(byte[] output, int outOff) + { + return DoFinal(output, outOff, GetDigestSize()); + } + + public virtual int DoFinal(byte[] output, int outOff, int outLen) + { + Absorb(new byte[]{ 0x0F }, 0, 4); + + Squeeze(output, outOff, ((long)outLen) * 8); + + Reset(); + + return outLen; + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + return DoFinal(output, outOff, GetDigestSize(), partialByte, partialBits); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int DoFinal(byte[] output, int outOff, int outLen, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 4; + + if (finalBits >= 8) + { + oneByte[0] = (byte)finalInput; + Absorb(oneByte, 0, 8); + finalBits -= 8; + finalInput >>= 8; + } + + if (finalBits > 0) + { + oneByte[0] = (byte)finalInput; + Absorb(oneByte, 0, finalBits); + } + + Squeeze(output, outOff, ((long)outLen) * 8); + + Reset(); + + return outLen; + } + + public override IMemoable Copy() + { + return new ShakeDigest(this); + } + } +} |