summary refs log tree commit diff
path: root/crypto/src
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2015-09-06 16:37:29 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2015-09-06 16:37:29 +0700
commitd364d3fcd41af5e71e234dacef8aac5064fbc010 (patch)
tree2008007bdbd41e63d59a104db07a3d7471392962 /crypto/src
parentAdd Miller-Rabin methods (diff)
downloadBouncyCastle.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.cs22
-rw-r--r--crypto/src/crypto/digests/KeccakDigest.cs534
-rw-r--r--crypto/src/crypto/digests/SHA3Digest.cs538
-rw-r--r--crypto/src/crypto/digests/ShakeDigest.cs111
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);
+        }
+    }
+}