summary refs log tree commit diff
path: root/crypto/src
diff options
context:
space:
mode:
authorGefei Li <gefei.li@keyfactor.com>2023-01-31 07:34:53 +0000
committerGefei Li <gefei.li@keyfactor.com>2023-01-31 07:34:53 +0000
commitb61cdb6df0345886950237f71a9ce52706e783a8 (patch)
tree4895072dda1eb6165e2c601de40465b29e76714f /crypto/src
parentAdd Photon-Beetle to the master branch (diff)
parentAdd test of Elephyant and decryption part (diff)
downloadBouncyCastle.NET-ed25519-b61cdb6df0345886950237f71a9ce52706e783a8.tar.xz
Merge branch 'elephant' into 'master'
Elephant

See merge request root/bc-csharp!3
Diffstat (limited to 'crypto/src')
-rw-r--r--crypto/src/crypto/engines/ElephantEngine.cs592
1 files changed, 592 insertions, 0 deletions
diff --git a/crypto/src/crypto/engines/ElephantEngine.cs b/crypto/src/crypto/engines/ElephantEngine.cs
new file mode 100644
index 000000000..e3d9ca670
--- /dev/null
+++ b/crypto/src/crypto/engines/ElephantEngine.cs
@@ -0,0 +1,592 @@
+using System;
+using System.IO;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+
+/**
+ * Elephant AEAD v2, based on the current round 3 submission, https://www.esat.kuleuven.be/cosic/elephant/
+ * Reference C implementation: https://github.com/TimBeyne/Elephant
+ * Specification: https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/elephant-spec-final.pdf
+ */
+namespace Org.BouncyCastle.Crypto.Engines
+{
+    public class ElephantEngine : IAeadBlockCipher
+    {
+        public enum ElephantParameters
+        {
+            elephant160,
+            elephant176,
+            elephant200
+        }
+        private bool forEncryption;
+        private readonly string algorithmName;
+        private ElephantParameters parameters;
+        private int BLOCK_SIZE;
+        private int nBits;
+        private int nSBox;
+        private int nRounds;
+        private byte lfsrIV;
+        private byte[] npub;
+        private byte[] expanded_key;
+        private byte[] tag;
+        private byte CRYPTO_KEYBYTES = 16;
+        private byte CRYPTO_NPUBBYTES = 12;
+        private byte CRYPTO_ABYTES;
+        private bool initialised;
+        private MemoryStream aadData = new MemoryStream();
+        private MemoryStream message = new MemoryStream();
+
+        private readonly byte[] sBoxLayer = {
+        (byte)0xee, (byte)0xed, (byte)0xeb, (byte)0xe0, (byte)0xe2, (byte)0xe1, (byte)0xe4, (byte)0xef, (byte)0xe7, (byte)0xea, (byte)0xe8, (byte)0xe5, (byte)0xe9, (byte)0xec, (byte)0xe3, (byte)0xe6,
+        (byte)0xde, (byte)0xdd, (byte)0xdb, (byte)0xd0, (byte)0xd2, (byte)0xd1, (byte)0xd4, (byte)0xdf, (byte)0xd7, (byte)0xda, (byte)0xd8, (byte)0xd5, (byte)0xd9, (byte)0xdc, (byte)0xd3, (byte)0xd6,
+        (byte)0xbe, (byte)0xbd, (byte)0xbb, (byte)0xb0, (byte)0xb2, (byte)0xb1, (byte)0xb4, (byte)0xbf, (byte)0xb7, (byte)0xba, (byte)0xb8, (byte)0xb5, (byte)0xb9, (byte)0xbc, (byte)0xb3, (byte)0xb6,
+        (byte)0x0e, (byte)0x0d, (byte)0x0b, (byte)0x00, (byte)0x02, (byte)0x01, (byte)0x04, (byte)0x0f, (byte)0x07, (byte)0x0a, (byte)0x08, (byte)0x05, (byte)0x09, (byte)0x0c, (byte)0x03, (byte)0x06,
+        (byte)0x2e, (byte)0x2d, (byte)0x2b, (byte)0x20, (byte)0x22, (byte)0x21, (byte)0x24, (byte)0x2f, (byte)0x27, (byte)0x2a, (byte)0x28, (byte)0x25, (byte)0x29, (byte)0x2c, (byte)0x23, (byte)0x26,
+        (byte)0x1e, (byte)0x1d, (byte)0x1b, (byte)0x10, (byte)0x12, (byte)0x11, (byte)0x14, (byte)0x1f, (byte)0x17, (byte)0x1a, (byte)0x18, (byte)0x15, (byte)0x19, (byte)0x1c, (byte)0x13, (byte)0x16,
+        (byte)0x4e, (byte)0x4d, (byte)0x4b, (byte)0x40, (byte)0x42, (byte)0x41, (byte)0x44, (byte)0x4f, (byte)0x47, (byte)0x4a, (byte)0x48, (byte)0x45, (byte)0x49, (byte)0x4c, (byte)0x43, (byte)0x46,
+        (byte)0xfe, (byte)0xfd, (byte)0xfb, (byte)0xf0, (byte)0xf2, (byte)0xf1, (byte)0xf4, (byte)0xff, (byte)0xf7, (byte)0xfa, (byte)0xf8, (byte)0xf5, (byte)0xf9, (byte)0xfc, (byte)0xf3, (byte)0xf6,
+        (byte)0x7e, (byte)0x7d, (byte)0x7b, (byte)0x70, (byte)0x72, (byte)0x71, (byte)0x74, (byte)0x7f, (byte)0x77, (byte)0x7a, (byte)0x78, (byte)0x75, (byte)0x79, (byte)0x7c, (byte)0x73, (byte)0x76,
+        (byte)0xae, (byte)0xad, (byte)0xab, (byte)0xa0, (byte)0xa2, (byte)0xa1, (byte)0xa4, (byte)0xaf, (byte)0xa7, (byte)0xaa, (byte)0xa8, (byte)0xa5, (byte)0xa9, (byte)0xac, (byte)0xa3, (byte)0xa6,
+        (byte)0x8e, (byte)0x8d, (byte)0x8b, (byte)0x80, (byte)0x82, (byte)0x81, (byte)0x84, (byte)0x8f, (byte)0x87, (byte)0x8a, (byte)0x88, (byte)0x85, (byte)0x89, (byte)0x8c, (byte)0x83, (byte)0x86,
+        (byte)0x5e, (byte)0x5d, (byte)0x5b, (byte)0x50, (byte)0x52, (byte)0x51, (byte)0x54, (byte)0x5f, (byte)0x57, (byte)0x5a, (byte)0x58, (byte)0x55, (byte)0x59, (byte)0x5c, (byte)0x53, (byte)0x56,
+        (byte)0x9e, (byte)0x9d, (byte)0x9b, (byte)0x90, (byte)0x92, (byte)0x91, (byte)0x94, (byte)0x9f, (byte)0x97, (byte)0x9a, (byte)0x98, (byte)0x95, (byte)0x99, (byte)0x9c, (byte)0x93, (byte)0x96,
+        (byte)0xce, (byte)0xcd, (byte)0xcb, (byte)0xc0, (byte)0xc2, (byte)0xc1, (byte)0xc4, (byte)0xcf, (byte)0xc7, (byte)0xca, (byte)0xc8, (byte)0xc5, (byte)0xc9, (byte)0xcc, (byte)0xc3, (byte)0xc6,
+        (byte)0x3e, (byte)0x3d, (byte)0x3b, (byte)0x30, (byte)0x32, (byte)0x31, (byte)0x34, (byte)0x3f, (byte)0x37, (byte)0x3a, (byte)0x38, (byte)0x35, (byte)0x39, (byte)0x3c, (byte)0x33, (byte)0x36,
+        (byte)0x6e, (byte)0x6d, (byte)0x6b, (byte)0x60, (byte)0x62, (byte)0x61, (byte)0x64, (byte)0x6f, (byte)0x67, (byte)0x6a, (byte)0x68, (byte)0x65, (byte)0x69, (byte)0x6c, (byte)0x63, (byte)0x66
+    };
+
+        private readonly byte[] KeccakRoundConstants = {
+        (byte)0x01, (byte)0x82, (byte)0x8a, (byte)0x00, (byte)0x8b, (byte)0x01, (byte)0x81, (byte)0x09, (byte)0x8a,
+        (byte)0x88, (byte)0x09, (byte)0x0a, (byte)0x8b, (byte)0x8b, (byte)0x89, (byte)0x03, (byte)0x02, (byte)0x80
+    };
+
+        private readonly int[] KeccakRhoOffsets = { 0, 1, 6, 4, 3, 4, 4, 6, 7, 4, 3, 2, 3, 1, 7, 1, 5, 7, 5, 0, 2, 2, 5, 0, 6 };
+
+        public ElephantEngine(ElephantParameters parameters)
+        {
+            switch (parameters)
+            {
+                case ElephantParameters.elephant160:
+                    BLOCK_SIZE = 20;
+                    nBits = 160;
+                    nSBox = 20;
+                    nRounds = 80;
+                    lfsrIV = 0x75;
+                    CRYPTO_ABYTES = 8;
+                    algorithmName = "Elephant 160 AEAD";
+                    break;
+                case ElephantParameters.elephant176:
+                    BLOCK_SIZE = 22;
+                    nBits = 176;
+                    nSBox = 22;
+                    nRounds = 90;
+                    lfsrIV = 0x45;
+                    CRYPTO_ABYTES = 8;
+                    algorithmName = "Elephant 176 AEAD";
+                    break;
+                case ElephantParameters.elephant200:
+                    BLOCK_SIZE = 25;
+                    nRounds = 18;
+                    CRYPTO_ABYTES = 16;
+                    algorithmName = "Elephant 200 AEAD";
+                    break;
+                default:
+                    throw new ArgumentException("Invalid parameter settings for Elephant");
+            }
+            this.parameters = parameters;
+            initialised = false;
+            reset(false);
+        }
+
+        private void permutation(byte[] state)
+        {
+            switch (parameters)
+            {
+                case ElephantParameters.elephant160:
+                case ElephantParameters.elephant176:
+                    byte IV = lfsrIV;
+                    byte[] tmp = new byte[nSBox];
+                    for (int i = 0; i < nRounds; i++)
+                    {
+                        /* Add counter values */
+                        state[0] ^= IV;
+                        state[nSBox - 1] ^= (byte)(((IV & 0x01) << 7) | ((IV & 0x02) << 5) | ((IV & 0x04) << 3) | ((IV & 0x08)
+                            << 1) | ((IV & 0x10) >> 1) | ((IV & 0x20) >> 3) | ((IV & 0x40) >> 5) | ((IV & 0x80) >> 7));
+                        IV = (byte)(((IV << 1) | (((0x40 & IV) >> 6) ^ ((0x20 & IV) >> 5))) & 0x7f);
+                        /* sBoxLayer layer */
+                        for (int j = 0; j < nSBox; j++)
+                        {
+                            state[j] = sBoxLayer[(state[j] & 0xFF)];
+                        }
+                        /* pLayer */
+                        int PermutedBitNo;
+                        Arrays.Fill(tmp, (byte)0);
+                        for (int j = 0; j < nSBox; j++)
+                        {
+                            for (int k = 0; k < 8; k++)
+                            {
+                                PermutedBitNo = (j << 3) + k;
+                                if (PermutedBitNo != nBits - 1)
+                                {
+                                    PermutedBitNo = ((PermutedBitNo * nBits) >> 2) % (nBits - 1);
+                                }
+                                tmp[PermutedBitNo >> 3] ^= (byte)((((state[j] & 0xFF) >> k) & 0x1) << (PermutedBitNo & 7));
+                            }
+                        }
+                        Array.Copy(tmp, 0, state, 0, nSBox);
+                    }
+                    break;
+                case ElephantParameters.elephant200:
+                    for (int i = 0; i < nRounds; i++)
+                    {
+                        KeccakP200Round(state, i);
+                    }
+                    break;
+            }
+        }
+
+        private byte rotl(byte b)
+        {
+            return (byte)(((b & 0xFF) << 1) | ((b & 0xFF) >> 7));
+        }
+
+        private byte ROL8(byte a, int offset)
+        {
+            return (byte)((offset != 0) ? (((a & 0xFF) << offset) ^ ((a & 0xFF) >> (8 - offset))) : a);
+        }
+
+        private int index(int x, int y)
+        {
+            return x + y * 5;
+        }
+
+        private void KeccakP200Round(byte[] state, int indexRound)
+        {
+            int x, y;
+            byte[] tempA = new byte[25];
+            //theta
+            for (x = 0; x < 5; x++)
+            {
+                for (y = 0; y < 5; y++)
+                {
+                    tempA[x] ^= state[index(x, y)];
+                }
+            }
+            for (x = 0; x < 5; x++)
+            {
+                tempA[x + 5] = (byte)(ROL8(tempA[(x + 1) % 5], 1) ^ tempA[(x + 4) % 5]);
+            }
+            for (x = 0; x < 5; x++)
+            {
+                for (y = 0; y < 5; y++)
+                {
+                    state[index(x, y)] ^= tempA[x + 5];
+                }
+            }
+            //rho
+            for (x = 0; x < 5; x++)
+            {
+                for (y = 0; y < 5; y++)
+                {
+                    tempA[index(x, y)] = ROL8(state[index(x, y)], KeccakRhoOffsets[index(x, y)]);
+                }
+            }
+            //pi
+            for (x = 0; x < 5; x++)
+            {
+                for (y = 0; y < 5; y++)
+                {
+                    state[index(y, (2 * x + 3 * y) % 5)] = tempA[index(x, y)];
+                }
+            }
+            //chi
+            for (y = 0; y < 5; y++)
+            {
+                for (x = 0; x < 5; x++)
+                {
+                    tempA[x] = (byte)(state[index(x, y)] ^ ((~state[index((x + 1) % 5, y)]) & state[index((x + 2) % 5, y)]));
+                }
+                for (x = 0; x < 5; x++)
+                {
+                    state[index(x, y)] = tempA[x];
+                }
+            }
+            //iota
+            state[index(0, 0)] ^= KeccakRoundConstants[indexRound];
+        }
+
+
+        // State should be BLOCK_SIZE bytes long
+        // Note: input may be equal to output
+        private void lfsr_step(byte[] output, byte[] input)
+        {
+            switch (parameters)
+            {
+                case ElephantParameters.elephant160:
+                    output[BLOCK_SIZE - 1] = (byte)((((input[0] & 0xFF) << 3) | ((input[0] & 0xFF) >> 5)) ^
+                        ((input[3] & 0xFF) << 7) ^ ((input[13] & 0xFF) >> 7));
+                    break;
+                case ElephantParameters.elephant176:
+                    output[BLOCK_SIZE - 1] = (byte)(rotl(input[0]) ^ ((input[3] & 0xFF) << 7) ^ ((input[19] & 0xFF) >> 7));
+                    break;
+                case ElephantParameters.elephant200:
+                    output[BLOCK_SIZE - 1] = (byte)(rotl(input[0]) ^ rotl(input[2]) ^ (input[13] << 1));
+                    break;
+            }
+            Array.Copy(input, 1, output, 0, BLOCK_SIZE - 1);
+        }
+
+        private void xor_block(byte[] state, byte[] block, int bOff, int size)
+        {
+            for (int i = 0; i < size; ++i)
+            {
+                state[i] ^= block[i + bOff];
+            }
+        }
+
+        // Write the ith assocated data block to "output".
+        // The nonce is prepended and padding is added as required.
+        // adlen is the length of the associated data in bytes
+        private void get_ad_block(byte[] output, byte[] ad, int adlen, byte[] npub, int i)
+        {
+            int len = 0;
+            // First block contains nonce
+            // Remark: nonce may not be longer then BLOCK_SIZE
+            if (i == 0)
+            {
+                Array.Copy(npub, 0, output, 0, CRYPTO_NPUBBYTES);
+                len += CRYPTO_NPUBBYTES;
+            }
+            int block_offset = i * BLOCK_SIZE - ((i != 0) ? 1 : 0) * CRYPTO_NPUBBYTES;
+            // If adlen is divisible by BLOCK_SIZE, add an additional padding block
+            if (i != 0 && block_offset == adlen)
+            {
+                Arrays.Fill(output, 0, BLOCK_SIZE, (byte)0);
+                output[0] = 0x01;
+                return;
+            }
+            int r_outlen = BLOCK_SIZE - len;
+            int r_adlen = adlen - block_offset;
+            // Fill with associated data if available
+            if (r_outlen <= r_adlen)
+            { // enough AD
+                Array.Copy(ad, block_offset, output, len, r_outlen);
+            }
+            else
+            { // not enough AD, need to pad
+                if (r_adlen > 0) // ad might be nullptr
+                {
+                    Array.Copy(ad, block_offset, output, len, r_adlen);
+                }
+                Arrays.Fill(output, len + r_adlen, len + r_outlen, (byte)0);
+                output[len + r_adlen] = 0x01;
+            }
+        }
+
+        // Return the ith ciphertext block.
+        // clen is the length of the ciphertext in bytes
+        private void get_c_block(byte[] output, byte[] c, int cOff, int clen, int i)
+        {
+            int block_offset = i * BLOCK_SIZE;
+            // If clen is divisible by BLOCK_SIZE, add an additional padding block
+            if (block_offset == clen)
+            {
+                Arrays.Fill(output, 0, BLOCK_SIZE, (byte)0);
+                output[0] = 0x01;
+                return;
+            }
+            int r_clen = clen - block_offset;
+            // Fill with ciphertext if available
+            if (BLOCK_SIZE <= r_clen)
+            { // enough ciphertext
+                Array.Copy(c, cOff + block_offset, output, 0, BLOCK_SIZE);
+            }
+            else
+            { // not enough ciphertext, need to pad
+                if (r_clen > 0) // c might be nullptr
+                {
+                    Array.Copy(c, cOff + block_offset, output, 0, r_clen);
+                }
+                Arrays.Fill(output, r_clen, BLOCK_SIZE, (byte)0);
+                output[r_clen] = 0x01;
+            }
+        }
+
+
+
+        public void Init(bool forEncryption, ICipherParameters param)
+        {
+            this.forEncryption = forEncryption;
+            if (!(param is ParametersWithIV))
+            {
+                throw new ArgumentException(
+                    "Elephant init parameters must include an IV");
+            }
+
+            ParametersWithIV ivParams = (ParametersWithIV)param;
+
+            npub = ivParams.GetIV();
+
+            if (npub == null || npub.Length != 12)
+            {
+                throw new ArgumentException(
+                    "Elephant requires exactly 12 bytes of IV");
+            }
+
+            if (!(ivParams.Parameters is KeyParameter))
+            {
+                throw new ArgumentException(
+                    "Elephant init parameters must include a key");
+            }
+
+            KeyParameter key = (KeyParameter)ivParams.Parameters;
+            byte[] k = key.GetKey();
+            if (k.Length != 16)
+            {
+                throw new ArgumentException(
+                    "Elephant key must be 128 bits long");
+            }
+            // Storage for the expanded key L
+            expanded_key = new byte[BLOCK_SIZE];
+            Array.Copy(k, 0, expanded_key, 0, CRYPTO_KEYBYTES);
+            permutation(expanded_key);
+            initialised = true;
+            reset(false);
+        }
+
+
+        public string AlgorithmName => algorithmName;
+
+        public IBlockCipher UnderlyingCipher => throw new NotImplementedException();
+
+        public void ProcessAadByte(byte input)
+        {
+            aadData.Write(new byte[] { input }, 0, 1);
+        }
+
+
+        public void ProcessAadBytes(byte[] input, int inOff, int len)
+        {
+            if (inOff + len > input.Length)
+            {
+                throw new DataLengthException("input buffer too short");
+            }
+            aadData.Write(input, inOff, len);
+        }
+
+
+        public int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            message.Write(new byte[] { input }, 0, 1);
+            return 0;
+        }
+
+
+        public int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+        {
+            if (inOff + len > input.Length)
+            {
+                throw new DataLengthException("input buffer too short");
+            }
+            message.Write(input, inOff, len);
+            return 0;
+        }
+
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            if (!initialised)
+            {
+                throw new ArgumentException("Need call init function before encryption/decryption");
+            }
+            int mlen = (int)message.Length - (forEncryption ? 0 : CRYPTO_ABYTES);
+            if ((forEncryption && mlen + outOff + CRYPTO_ABYTES > output.Length) ||
+            (!forEncryption && mlen + outOff - CRYPTO_ABYTES > output.Length))
+            {
+                throw new OutputLengthException("output buffer is too short");
+            }
+            byte[] tag_buffer = new byte[BLOCK_SIZE];
+            byte[] m = message.GetBuffer();
+            byte[] ad = aadData.GetBuffer();
+            int adlen = (int)aadData.Length;
+            int nblocks_c = 1 + mlen / BLOCK_SIZE;
+            int nblocks_m = (mlen % BLOCK_SIZE) != 0 ? nblocks_c : nblocks_c - 1;
+            int nblocks_ad = 1 + (CRYPTO_NPUBBYTES + adlen) / BLOCK_SIZE;
+            int nb_it = System.Math.Max(nblocks_c + 1, nblocks_ad - 1);
+            // Buffers for storing previous, current and next mask
+            byte[] previous_mask = new byte[BLOCK_SIZE];
+            byte[] current_mask = new byte[BLOCK_SIZE];
+            byte[] next_mask = new byte[BLOCK_SIZE];
+            Array.Copy(expanded_key, 0, current_mask, 0, BLOCK_SIZE);
+            // Buffer to store current ciphertext/AD block
+            byte[] buffer = new byte[BLOCK_SIZE];
+            // Tag buffer and initialization of tag to first AD block
+            get_ad_block(tag_buffer, ad, adlen, npub, 0);
+            int offset = 0;
+            for (int i = 0; i < nb_it; ++i)
+            {
+                // Compute mask for the next message
+                lfsr_step(next_mask, current_mask);
+                if (i < nblocks_m)
+                {
+                    // Compute ciphertext block
+                    Array.Copy(npub, 0, buffer, 0, CRYPTO_NPUBBYTES);
+                    Arrays.Fill(buffer, CRYPTO_NPUBBYTES, BLOCK_SIZE, (byte)0);
+                    xor_block(buffer, current_mask, 0, BLOCK_SIZE);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    permutation(buffer);
+                    xor_block(buffer, current_mask, 0, BLOCK_SIZE);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    int r_size = (i == nblocks_m - 1) ? mlen - offset : BLOCK_SIZE;
+                    xor_block(buffer, m, offset, r_size);
+                    Array.Copy(buffer, 0, output, offset + outOff, r_size);
+                }
+                if (i > 0 && i <= nblocks_c)
+                {
+                    // Compute tag for ciphertext block
+                    if (forEncryption)
+                    {
+                        get_c_block(buffer, output, outOff, mlen, i - 1);
+                    }
+                    else
+                    {
+                        get_c_block(buffer, m, 0, mlen, i - 1);
+                    }
+                    xor_block(buffer, previous_mask, 0, BLOCK_SIZE);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    permutation(buffer);
+                    xor_block(buffer, previous_mask, 0, BLOCK_SIZE);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    xor_block(tag_buffer, buffer, 0, BLOCK_SIZE);
+                }
+                // If there is any AD left, compute tag for AD block
+                if (i + 1 < nblocks_ad)
+                {
+                    get_ad_block(buffer, ad, adlen, npub, i + 1);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    permutation(buffer);
+                    xor_block(buffer, next_mask, 0, BLOCK_SIZE);
+                    xor_block(tag_buffer, buffer, 0, BLOCK_SIZE);
+                }
+                // Cyclically shift the mask buffers
+                // Value of next_mask will be computed in the next iteration
+                byte[] temp = previous_mask;
+                previous_mask = current_mask;
+                current_mask = next_mask;
+                next_mask = temp;
+                offset += BLOCK_SIZE;
+            }
+            outOff += mlen;
+            tag = new byte[CRYPTO_ABYTES];
+            xor_block(tag_buffer, expanded_key, 0, BLOCK_SIZE);
+            permutation(tag_buffer);
+            xor_block(tag_buffer, expanded_key, 0, BLOCK_SIZE);
+            if (forEncryption)
+            {
+                Array.Copy(tag_buffer, 0, tag, 0, CRYPTO_ABYTES);
+                Array.Copy(tag, 0, output, outOff, tag.Length);
+                mlen += CRYPTO_ABYTES;
+            }
+            else
+            {
+                for (int i = 0; i < CRYPTO_ABYTES; ++i)
+                {
+                    if (tag_buffer[i] != m[mlen + i])
+                    {
+                        throw new ArgumentException("Mac does not match");
+                    }
+                }
+            }
+            reset(false);
+            return mlen;
+        }
+
+
+        public byte[] GetMac()
+        {
+            return tag;
+        }
+
+
+        public int GetUpdateOutputSize(int len)
+        {
+            return len;
+        }
+
+
+        public int GetOutputSize(int len)
+        {
+            return len + CRYPTO_ABYTES;
+        }
+
+
+        public void Reset()
+        {
+            reset(true);
+        }
+
+        private void reset(bool clearMac)
+        {
+            if (clearMac)
+            {
+                tag = null;
+            }
+            aadData.SetLength(0);
+            message.SetLength(0);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void ProcessAadBytes(ReadOnlySpan<byte> input)
+        {
+            aadData.Write(input);
+        }
+
+        public int ProcessByte(byte input, Span<byte> output)
+        {
+            message.Write(new byte[] { input });
+            return 0;
+        }
+
+        public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            message.Write(input.ToArray());
+            return 0;
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            byte[] rv;
+            if (forEncryption)
+            {
+                rv = new byte[message.Length + CRYPTO_ABYTES];
+            }
+            else
+            {
+                rv = new byte[message.Length - CRYPTO_ABYTES];
+            }
+            int len = DoFinal(rv, 0);
+            rv.AsSpan(0, len).CopyTo(output);
+            return rv.Length;
+
+        }
+#endif
+
+        public int GetKeyBytesSize()
+        {
+            return CRYPTO_KEYBYTES;
+        }
+
+        public int GetIVBytesSize()
+        {
+            return CRYPTO_NPUBBYTES;
+        }
+
+        public int GetBlockSize()
+        {
+            return BLOCK_SIZE;
+        }
+    }
+}
+