diff --git a/crypto/src/crypto/engines/ElephantEngine.cs b/crypto/src/crypto/engines/ElephantEngine.cs
new file mode 100644
index 000000000..b0ec4733c
--- /dev/null
+++ b/crypto/src/crypto/engines/ElephantEngine.cs
@@ -0,0 +1,533 @@
+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 : IAeadCipher
+ {
+ public enum ElephantParameters
+ {
+ elephant160,
+ elephant176,
+ elephant200
+ }
+
+ private ElephantParameters parameters;
+ private int BLOCK_SIZE;
+ private int nBits;
+ private int nSBox;
+ private int nRounds;
+ private byte lfsrIV;
+ private byte[] m;
+ private byte[] npub;
+ private byte[] expanded_key;
+ private byte[] tag_buffer;
+ private byte CRYPTO_KEYBYTES = 16;
+ private byte CRYPTO_NPUBBYTES = 12;
+ private byte CRYPTO_ABYTES;
+ private MemoryStream aadData = new MemoryStream();
+ private bool encrypted;
+ private bool aadFinished;
+
+ 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;
+ break;
+ case ElephantParameters.elephant176:
+ BLOCK_SIZE = 22;
+ nBits = 176;
+ nSBox = 22;
+ nRounds = 90;
+ lfsrIV = 0x45;
+ CRYPTO_ABYTES = 8;
+ break;
+ case ElephantParameters.elephant200:
+ BLOCK_SIZE = 25;
+ nRounds = 18;
+ CRYPTO_ABYTES = 16;
+ break;
+ }
+ this.parameters = parameters;
+ tag_buffer = new byte[BLOCK_SIZE];
+ 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;
+ }
+
+ 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
+ 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);
+ }
+
+ 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
+ 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
+ void get_c_block(byte[] output, byte[] c, int cOff, int clen, int i)
+ {
+ int block_offset = cOff + 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, block_offset, output, 0, BLOCK_SIZE);
+ }
+ else
+ { // not enough ciphertext, need to pad
+ if (r_clen > 0) // c might be nullptr
+ {
+ Array.Copy(c, 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)
+ {
+ /**
+ * Elephant encryption and decryption is completely symmetrical, so the
+ * 'forEncryption' is irrelevant.
+ */
+ 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);
+ }
+
+
+ public string AlgorithmName => "Elephant AEAD";
+
+ public void ProcessAadByte(byte input)
+ {
+ aadData.Write(new byte[] { input }, 0, 1);
+ }
+
+
+ public void ProcessAadBytes(byte[] input, int inOff, int len)
+ {
+ aadData.Write(input, inOff, len);
+ }
+
+
+ public int ProcessByte(byte input, byte[] output, int outOff)
+ {
+ return ProcessBytes(new byte[] { input }, 0, 1, output, outOff);
+ }
+
+
+ public int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+ {
+ if (encrypted)
+ {
+ throw new ArgumentException("Encryption has been processed");
+ }
+ m = new byte[len];
+ Array.Copy(input, inOff, m, 0, len);
+ byte[] ad = aadData.GetBuffer();
+ int mlen = len, 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
+ get_c_block(buffer, output, outOff, mlen, i - 1);//encrypt != 0 ? c : m
+ 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;
+ }
+ encrypted = true;
+ return 0;
+ }
+
+
+ public int DoFinal(byte[] output, int outOff)
+ {
+ byte[] tag = GetMac();
+ Array.Copy(tag, 0, output, outOff, tag.Length);
+ reset(false);
+ return 0;
+ }
+
+
+ public byte[] GetMac()
+ {
+ byte[] tag = new byte[CRYPTO_ABYTES];
+ if (!aadFinished)
+ {
+ xor_block(tag_buffer, expanded_key, 0, BLOCK_SIZE);
+ permutation(tag_buffer);
+ xor_block(tag_buffer, expanded_key, 0, BLOCK_SIZE);
+ }
+ Array.Copy(tag_buffer, 0, tag, 0, CRYPTO_ABYTES);
+ 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)
+ {
+ aadData.SetLength(0);
+ }
+ encrypted = false;
+ aadFinished = false;
+ }
+#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)
+ {
+ byte[] rv = new byte[1];
+ ProcessBytes(new byte[] { input }, 0, 1, rv, 0);
+ rv.AsSpan(0, 1).CopyTo(output);
+ return 1;
+ }
+
+ public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ byte[] rv = new byte[input.Length];
+ ProcessBytes(input.ToArray(), 0, rv.Length, rv, 0);
+ rv.AsSpan(0, rv.Length).CopyTo(output);
+ return rv.Length;
+ }
+
+ public int DoFinal(Span<byte> output)
+ {
+ byte[] tag = GetMac();
+ tag.AsSpan(0, tag.Length).CopyTo(output);
+ reset(false);
+ return tag.Length;
+ }
+#endif
+ }
+}
+
|