diff --git a/crypto/src/crypto/modes/KCcmBlockCipher.cs b/crypto/src/crypto/modes/KCcmBlockCipher.cs
new file mode 100644
index 000000000..b5f121cef
--- /dev/null
+++ b/crypto/src/crypto/modes/KCcmBlockCipher.cs
@@ -0,0 +1,475 @@
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+ public class KCcmBlockCipher: IAeadBlockCipher
+ {
+ private static readonly int BYTES_IN_INT = 4;
+ private static readonly int BITS_IN_BYTE = 8;
+
+ private static readonly int MAX_MAC_BIT_LENGTH = 512;
+ private static readonly int MIN_MAC_BIT_LENGTH = 64;
+
+ private IBlockCipher engine;
+
+ private int macSize;
+ private bool forEncryption;
+
+ private byte[] initialAssociatedText;
+ private byte[] mac;
+ private byte[] macBlock;
+
+ private byte[] nonce;
+
+ private byte[] G1;
+ private byte[] buffer;
+
+ private byte[] s;
+ private byte[] counter;
+
+ private readonly MemoryStream associatedText = new MemoryStream();
+ private readonly MemoryStream data = new MemoryStream();
+
+ /*
+ * Nb is a parameter specified in CCM mode of DSTU7624 standard.
+ * This parameter specifies maximum possible length of input. It should
+ * be calculated as follows: Nb = 1/8 * (-3 + log[2]Nmax) + 1,
+ * where Nmax - length of input message in bits. For practical reasons
+ * Nmax usually less than 4Gb, e.g. for Nmax = 2^32 - 1, Nb = 4.
+ *
+ */
+ private int Nb_ = 4;
+
+ public void setNb(int Nb)
+ {
+ if (Nb == 4 || Nb == 6 || Nb == 8)
+ {
+ Nb_ = Nb;
+ }
+ else
+ {
+ throw new ArgumentException("Nb = 4 is recommended by DSTU7624 but can be changed to only 6 or 8 in this implementation");
+ }
+ }
+
+ public KCcmBlockCipher(IBlockCipher engine)
+ {
+ this.engine = engine;
+ this.macSize = engine.GetBlockSize();
+ this.nonce = new byte[engine.GetBlockSize()];
+ this.initialAssociatedText = new byte[engine.GetBlockSize()];
+ this.mac = new byte[engine.GetBlockSize()];
+ this.macBlock = new byte[engine.GetBlockSize()];
+ this.G1 = new byte[engine.GetBlockSize()];
+ this.buffer = new byte[engine.GetBlockSize()];
+ this.s = new byte[engine.GetBlockSize()];
+ this.counter = new byte[engine.GetBlockSize()];
+ }
+
+ public virtual void Init(bool forEncryption, ICipherParameters parameters)
+ {
+
+ ICipherParameters cipherParameters;
+ if (parameters is AeadParameters)
+ {
+
+ AeadParameters param = (AeadParameters)parameters;
+
+ if (param.MacSize > MAX_MAC_BIT_LENGTH || param.MacSize < MIN_MAC_BIT_LENGTH || param.MacSize % 8 != 0)
+ {
+ throw new ArgumentException("Invalid mac size specified");
+ }
+
+ nonce = param.GetNonce();
+ macSize = param.MacSize / BITS_IN_BYTE;
+ initialAssociatedText = param.GetAssociatedText();
+ cipherParameters = param.Key;
+ }
+ else if (parameters is ParametersWithIV)
+ {
+ nonce = ((ParametersWithIV)parameters).GetIV();
+ macSize = engine.GetBlockSize(); // use default blockSize for MAC if it is not specified
+ initialAssociatedText = null;
+ cipherParameters = ((ParametersWithIV)parameters).Parameters;
+ }
+ else
+ {
+ throw new ArgumentException("Invalid parameters specified");
+ }
+
+ this.mac = new byte[macSize];
+ this.forEncryption = forEncryption;
+ engine.Init(true, cipherParameters);
+
+ counter[0] = 0x01; // defined in standard
+
+ if (initialAssociatedText != null)
+ {
+ ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length);
+ }
+ }
+
+ public virtual String AlgorithmName
+ {
+ get
+ {
+ return engine.AlgorithmName + "/KCCM";
+ }
+ }
+
+ public virtual int GetBlockSize()
+ {
+ return engine.GetBlockSize();
+ }
+
+ public virtual IBlockCipher GetUnderlyingCipher()
+ {
+ return engine;
+ }
+
+ public virtual void ProcessAadByte(byte input)
+ {
+ associatedText.WriteByte(input);
+ }
+
+ public virtual void ProcessAadBytes(byte[] input, int inOff, int len)
+ {
+ associatedText.Write(input, inOff, len);
+ }
+
+ private void ProcessAAD(byte[] assocText, int assocOff, int assocLen, int dataLen)
+ {
+ if (assocLen - assocOff < engine.GetBlockSize())
+ {
+ throw new ArgumentException("authText buffer too short");
+ }
+ if (assocLen % engine.GetBlockSize() != 0)
+ {
+ throw new ArgumentException("padding not supported");
+ }
+
+ Array.Copy(nonce, 0, G1, 0, nonce.Length - Nb_ - 1);
+
+ intToBytes(dataLen, buffer, 0); // for G1
+
+ Array.Copy(buffer, 0, G1, nonce.Length - Nb_ - 1, BYTES_IN_INT);
+
+ G1[G1.Length - 1] = getFlag(true, macSize);
+
+ engine.ProcessBlock(G1, 0, macBlock, 0);
+
+ intToBytes(assocLen, buffer, 0); // for G2
+
+ if (assocLen <= engine.GetBlockSize() - Nb_)
+ {
+ for (int byteIndex = 0; byteIndex < assocLen; byteIndex++)
+ {
+ buffer[byteIndex + Nb_] ^= assocText[assocOff + byteIndex];
+ }
+
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ macBlock[byteIndex] ^= buffer[byteIndex];
+ }
+
+ engine.ProcessBlock(macBlock, 0, macBlock, 0);
+
+ return;
+ }
+
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ macBlock[byteIndex] ^= buffer[byteIndex];
+ }
+
+ engine.ProcessBlock(macBlock, 0, macBlock, 0);
+
+ int authLen = assocLen;
+ while (authLen != 0)
+ {
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ macBlock[byteIndex] ^= assocText[byteIndex + assocOff];
+ }
+
+ engine.ProcessBlock(macBlock, 0, macBlock, 0);
+
+ assocOff += engine.GetBlockSize();
+ authLen -= engine.GetBlockSize();
+ }
+ }
+
+ public virtual int ProcessByte(byte input, byte[] output, int outOff)
+ {
+ data.WriteByte(input);
+
+ return 0;
+ }
+
+ public virtual int ProcessBytes(byte[] input, int inOff, int inLen, byte[] output, int outOff)
+ {
+ if (input.Length< (inOff + inLen))
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+ data.Write(input, inOff, inLen);
+
+ return 0;
+ }
+
+ public int ProcessPacket(byte[] input, int inOff, int len, byte[] output, int outOff)
+ {
+ if (input.Length - inOff<len)
+ {
+ throw new ArgumentException("input buffer too short");
+ }
+
+ if (output.Length - outOff<len)
+ {
+ throw new ArgumentException("output buffer too short");
+ }
+
+ if (associatedText.Length > 0)
+ {
+ if (forEncryption)
+ {
+ ProcessAAD(associatedText.GetBuffer(), 0, (int)associatedText.Length, (int)data.Length);
+ }
+ else
+ {
+ ProcessAAD(associatedText.GetBuffer(), 0, (int)associatedText.Length, (int)data.Length - macSize);
+ }
+ }
+
+ if (forEncryption)
+ {
+ if ((len % engine.GetBlockSize()) != 0)
+ {
+ throw new DataLengthException("partial blocks not supported");
+ }
+
+ CalculateMac(input, inOff, len);
+ engine.ProcessBlock(nonce, 0, s, 0);
+
+ int totalLength = len;
+ while (totalLength > 0)
+ {
+ ProcessBlock(input, inOff, len, output, outOff);
+ totalLength -= engine.GetBlockSize();
+ inOff += engine.GetBlockSize();
+ outOff += engine.GetBlockSize();
+ }
+
+ for (int byteIndex = 0; byteIndex<counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, 0, buffer, 0);
+
+ for (int byteIndex = 0; byteIndex<macSize; byteIndex++)
+ {
+ output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ macBlock[byteIndex]);
+ }
+
+ Reset();
+
+ return len + macSize;
+ }
+ else
+ {
+ if ((len - macSize) % engine.GetBlockSize() != 0)
+ {
+ throw new DataLengthException("partial blocks not supported");
+ }
+
+ engine.ProcessBlock(nonce, 0, s, 0);
+
+ int blocks = len / engine.GetBlockSize();
+
+ for (int blockNum = 0; blockNum<blocks; blockNum++)
+ {
+ ProcessBlock(input, inOff, len, output, outOff);
+
+ inOff += engine.GetBlockSize();
+ outOff += engine.GetBlockSize();
+ }
+
+ if (len > inOff)
+ {
+ for (int byteIndex = 0; byteIndex<counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, 0, buffer, 0);
+
+ for (int byteIndex = 0; byteIndex<macSize; byteIndex++)
+ {
+ output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+ }
+ outOff += macSize;
+ }
+
+ for (int byteIndex = 0; byteIndex<counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, 0, buffer, 0);
+
+ Array.Copy(output, outOff - macSize, buffer, 0, macSize);
+
+ CalculateMac(output, 0, outOff - macSize);
+
+ Array.Copy(macBlock, 0, mac, 0, macSize);
+
+ byte[] calculatedMac = new byte[macSize];
+
+ Array.Copy(buffer, 0, calculatedMac, 0, macSize);
+
+ if (!Arrays.ConstantTimeAreEqual(mac, calculatedMac))
+ {
+ throw new InvalidCipherTextException("mac check failed");
+ }
+
+ Reset();
+
+ return len;
+ }
+ }
+
+ private void ProcessBlock(byte[] input, int inOff, int len, byte[] output, int outOff)
+ {
+
+ for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, 0, buffer, 0);
+
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+ }
+ }
+
+ private void CalculateMac(byte[] authText, int authOff, int len)
+ {
+ int totalLen = len;
+ while (totalLen > 0)
+ {
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ macBlock[byteIndex] ^= authText[authOff + byteIndex];
+ }
+
+ engine.ProcessBlock(macBlock, 0, macBlock, 0);
+
+ totalLen -= engine.GetBlockSize();
+ authOff += engine.GetBlockSize();
+ }
+ }
+
+ public virtual int DoFinal(byte[] output, int outOff)
+ {
+ int len = ProcessPacket(data.GetBuffer(), 0, (int)data.Length, output, outOff);
+
+ Reset();
+
+ return len;
+ }
+
+ public virtual byte[] GetMac()
+ {
+ Array.Copy(macBlock, 0, mac, 0, macSize);
+ return Arrays.Clone(mac);
+ }
+
+ public virtual int GetUpdateOutputSize(int len)
+ {
+ return len;
+ }
+
+ public virtual int GetOutputSize(int len)
+ {
+ return len + macSize;
+ }
+
+ public virtual void Reset()
+ {
+ Arrays.Fill(G1, (byte)0);
+ Arrays.Fill(buffer, (byte)0);
+ Arrays.Fill(counter, (byte)0);
+ counter[0] = 0x01;
+ data.SetLength(0);
+ associatedText.SetLength(0);
+
+ if (initialAssociatedText != null)
+ {
+ ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length);
+ }
+ }
+
+ private void intToBytes(
+ int num,
+ byte[] outBytes,
+ int outOff)
+ {
+ outBytes[outOff + 3] = (byte)(num >> 24);
+ outBytes[outOff + 2] = (byte)(num >> 16);
+ outBytes[outOff + 1] = (byte)(num >> 8);
+ outBytes[outOff] = (byte)num;
+ }
+
+ private byte getFlag(bool authTextPresents, int macSize)
+ {
+ StringBuilder flagByte = new StringBuilder();
+
+ if (authTextPresents)
+ {
+ flagByte.Append("1");
+ }
+ else
+ {
+ flagByte.Append("0");
+ }
+
+
+ switch (macSize)
+ {
+ case 8:
+ flagByte.Append("010"); // binary 2
+ break;
+ case 16:
+ flagByte.Append("011"); // binary 3
+ break;
+ case 32:
+ flagByte.Append("100"); // binary 4
+ break;
+ case 48:
+ flagByte.Append("101"); // binary 5
+ break;
+ case 64:
+ flagByte.Append("110"); // binary 6
+ break;
+ }
+
+ String binaryNb = Convert.ToString(Nb_ - 1, 2);
+ while (binaryNb.Length < 4)
+ {
+ binaryNb = new StringBuilder(binaryNb).Insert(0, "0").ToString();
+ }
+
+ flagByte.Append(binaryNb);
+
+ return (byte)Convert.ToInt32(flagByte.ToString(), 2);
+ }
+ }
+}
|