320 lines
7.9 KiB
C#
320 lines
7.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using Org.BouncyCastle.Crypto.Macs;
|
|
using Org.BouncyCastle.Crypto.Parameters;
|
|
using Org.BouncyCastle.Utilities;
|
|
|
|
namespace Org.BouncyCastle.Crypto.Modes;
|
|
|
|
public class CcmBlockCipher : IAeadBlockCipher
|
|
{
|
|
private static readonly int BlockSize = 16;
|
|
|
|
private readonly IBlockCipher cipher;
|
|
|
|
private readonly byte[] macBlock;
|
|
|
|
private bool forEncryption;
|
|
|
|
private byte[] nonce;
|
|
|
|
private byte[] initialAssociatedText;
|
|
|
|
private int macSize;
|
|
|
|
private ICipherParameters keyParam;
|
|
|
|
private readonly MemoryStream associatedText = new MemoryStream();
|
|
|
|
private readonly MemoryStream data = new MemoryStream();
|
|
|
|
public virtual string AlgorithmName => cipher.AlgorithmName + "/CCM";
|
|
|
|
public CcmBlockCipher(IBlockCipher cipher)
|
|
{
|
|
this.cipher = cipher;
|
|
macBlock = new byte[BlockSize];
|
|
if (cipher.GetBlockSize() != BlockSize)
|
|
{
|
|
throw new ArgumentException("cipher required with a block size of " + BlockSize + ".");
|
|
}
|
|
}
|
|
|
|
public virtual IBlockCipher GetUnderlyingCipher()
|
|
{
|
|
return cipher;
|
|
}
|
|
|
|
public virtual void Init(bool forEncryption, ICipherParameters parameters)
|
|
{
|
|
this.forEncryption = forEncryption;
|
|
ICipherParameters cipherParameters;
|
|
if (parameters is AeadParameters)
|
|
{
|
|
AeadParameters aeadParameters = (AeadParameters)parameters;
|
|
nonce = aeadParameters.GetNonce();
|
|
initialAssociatedText = aeadParameters.GetAssociatedText();
|
|
macSize = aeadParameters.MacSize / 8;
|
|
cipherParameters = aeadParameters.Key;
|
|
}
|
|
else
|
|
{
|
|
if (!(parameters is ParametersWithIV))
|
|
{
|
|
throw new ArgumentException("invalid parameters passed to CCM");
|
|
}
|
|
ParametersWithIV parametersWithIV = (ParametersWithIV)parameters;
|
|
nonce = parametersWithIV.GetIV();
|
|
initialAssociatedText = null;
|
|
macSize = macBlock.Length / 2;
|
|
cipherParameters = parametersWithIV.Parameters;
|
|
}
|
|
if (cipherParameters != null)
|
|
{
|
|
keyParam = cipherParameters;
|
|
}
|
|
if (nonce == null || nonce.Length < 7 || nonce.Length > 13)
|
|
{
|
|
throw new ArgumentException("nonce must have length from 7 to 13 octets");
|
|
}
|
|
Reset();
|
|
}
|
|
|
|
public virtual int GetBlockSize()
|
|
{
|
|
return cipher.GetBlockSize();
|
|
}
|
|
|
|
public virtual void ProcessAadByte(byte input)
|
|
{
|
|
associatedText.WriteByte(input);
|
|
}
|
|
|
|
public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
|
|
{
|
|
associatedText.Write(inBytes, inOff, len);
|
|
}
|
|
|
|
public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
|
|
{
|
|
data.WriteByte(input);
|
|
return 0;
|
|
}
|
|
|
|
public virtual int ProcessBytes(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
|
|
{
|
|
Check.DataLength(inBytes, inOff, inLen, "Input buffer too short");
|
|
data.Write(inBytes, inOff, inLen);
|
|
return 0;
|
|
}
|
|
|
|
public virtual int DoFinal(byte[] outBytes, int outOff)
|
|
{
|
|
byte[] buffer = data.GetBuffer();
|
|
int inLen = (int)data.Position;
|
|
int result = ProcessPacket(buffer, 0, inLen, outBytes, outOff);
|
|
Reset();
|
|
return result;
|
|
}
|
|
|
|
public virtual void Reset()
|
|
{
|
|
cipher.Reset();
|
|
associatedText.SetLength(0L);
|
|
data.SetLength(0L);
|
|
}
|
|
|
|
public virtual byte[] GetMac()
|
|
{
|
|
return Arrays.CopyOfRange(macBlock, 0, macSize);
|
|
}
|
|
|
|
public virtual int GetUpdateOutputSize(int len)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual int GetOutputSize(int len)
|
|
{
|
|
int num = (int)data.Length + len;
|
|
if (forEncryption)
|
|
{
|
|
return num + macSize;
|
|
}
|
|
if (num >= macSize)
|
|
{
|
|
return num - macSize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen)
|
|
{
|
|
byte[] array;
|
|
if (forEncryption)
|
|
{
|
|
array = new byte[inLen + macSize];
|
|
}
|
|
else
|
|
{
|
|
if (inLen < macSize)
|
|
{
|
|
throw new InvalidCipherTextException("data too short");
|
|
}
|
|
array = new byte[inLen - macSize];
|
|
}
|
|
ProcessPacket(input, inOff, inLen, array, 0);
|
|
return array;
|
|
}
|
|
|
|
public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff)
|
|
{
|
|
if (keyParam == null)
|
|
{
|
|
throw new InvalidOperationException("CCM cipher unitialized.");
|
|
}
|
|
int num = nonce.Length;
|
|
int num2 = 15 - num;
|
|
if (num2 < 4)
|
|
{
|
|
int num3 = 1 << 8 * num2;
|
|
if (inLen >= num3)
|
|
{
|
|
throw new InvalidOperationException("CCM packet too large for choice of q.");
|
|
}
|
|
}
|
|
byte[] array = new byte[BlockSize];
|
|
array[0] = (byte)((num2 - 1) & 7);
|
|
nonce.CopyTo(array, 1);
|
|
IBlockCipher blockCipher = new SicBlockCipher(cipher);
|
|
blockCipher.Init(forEncryption, new ParametersWithIV(keyParam, array));
|
|
int i = inOff;
|
|
int num4 = outOff;
|
|
int num5;
|
|
if (forEncryption)
|
|
{
|
|
num5 = inLen + macSize;
|
|
Check.OutputLength(output, outOff, num5, "Output buffer too short.");
|
|
CalculateMac(input, inOff, inLen, macBlock);
|
|
byte[] array2 = new byte[BlockSize];
|
|
blockCipher.ProcessBlock(macBlock, 0, array2, 0);
|
|
for (; i < inOff + inLen - BlockSize; i += BlockSize)
|
|
{
|
|
blockCipher.ProcessBlock(input, i, output, num4);
|
|
num4 += BlockSize;
|
|
}
|
|
byte[] array3 = new byte[BlockSize];
|
|
Array.Copy(input, i, array3, 0, inLen + inOff - i);
|
|
blockCipher.ProcessBlock(array3, 0, array3, 0);
|
|
Array.Copy(array3, 0, output, num4, inLen + inOff - i);
|
|
Array.Copy(array2, 0, output, outOff + inLen, macSize);
|
|
}
|
|
else
|
|
{
|
|
if (inLen < macSize)
|
|
{
|
|
throw new InvalidCipherTextException("data too short");
|
|
}
|
|
num5 = inLen - macSize;
|
|
Check.OutputLength(output, outOff, num5, "Output buffer too short.");
|
|
Array.Copy(input, inOff + num5, macBlock, 0, macSize);
|
|
blockCipher.ProcessBlock(macBlock, 0, macBlock, 0);
|
|
for (int j = macSize; j != macBlock.Length; j++)
|
|
{
|
|
macBlock[j] = 0;
|
|
}
|
|
for (; i < inOff + num5 - BlockSize; i += BlockSize)
|
|
{
|
|
blockCipher.ProcessBlock(input, i, output, num4);
|
|
num4 += BlockSize;
|
|
}
|
|
byte[] array4 = new byte[BlockSize];
|
|
Array.Copy(input, i, array4, 0, num5 - (i - inOff));
|
|
blockCipher.ProcessBlock(array4, 0, array4, 0);
|
|
Array.Copy(array4, 0, output, num4, num5 - (i - inOff));
|
|
byte[] b = new byte[BlockSize];
|
|
CalculateMac(output, outOff, num5, b);
|
|
if (!Arrays.ConstantTimeAreEqual(macBlock, b))
|
|
{
|
|
throw new InvalidCipherTextException("mac check in CCM failed");
|
|
}
|
|
}
|
|
return num5;
|
|
}
|
|
|
|
private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
|
|
{
|
|
IMac mac = new CbcBlockCipherMac(cipher, macSize * 8);
|
|
mac.Init(keyParam);
|
|
byte[] array = new byte[16];
|
|
byte[] array2;
|
|
if (HasAssociatedText())
|
|
{
|
|
(array2 = array)[0] = (byte)(array2[0] | 0x40);
|
|
}
|
|
(array2 = array)[0] = (byte)(array2[0] | (byte)((((mac.GetMacSize() - 2) / 2) & 7) << 3));
|
|
(array2 = array)[0] = (byte)(array2[0] | (byte)((15 - nonce.Length - 1) & 7));
|
|
Array.Copy(nonce, 0, array, 1, nonce.Length);
|
|
int num = dataLen;
|
|
int num2 = 1;
|
|
while (num > 0)
|
|
{
|
|
array[^num2] = (byte)(num & 0xFF);
|
|
num >>= 8;
|
|
num2++;
|
|
}
|
|
mac.BlockUpdate(array, 0, array.Length);
|
|
if (HasAssociatedText())
|
|
{
|
|
int associatedTextLength = GetAssociatedTextLength();
|
|
int num3;
|
|
if (associatedTextLength < 65280)
|
|
{
|
|
mac.Update((byte)(associatedTextLength >> 8));
|
|
mac.Update((byte)associatedTextLength);
|
|
num3 = 2;
|
|
}
|
|
else
|
|
{
|
|
mac.Update(byte.MaxValue);
|
|
mac.Update(254);
|
|
mac.Update((byte)(associatedTextLength >> 24));
|
|
mac.Update((byte)(associatedTextLength >> 16));
|
|
mac.Update((byte)(associatedTextLength >> 8));
|
|
mac.Update((byte)associatedTextLength);
|
|
num3 = 6;
|
|
}
|
|
if (initialAssociatedText != null)
|
|
{
|
|
mac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
|
|
}
|
|
if (associatedText.Position > 0)
|
|
{
|
|
byte[] buffer = associatedText.GetBuffer();
|
|
int len = (int)associatedText.Position;
|
|
mac.BlockUpdate(buffer, 0, len);
|
|
}
|
|
num3 = (num3 + associatedTextLength) % 16;
|
|
if (num3 != 0)
|
|
{
|
|
for (int i = num3; i < 16; i++)
|
|
{
|
|
mac.Update(0);
|
|
}
|
|
}
|
|
}
|
|
mac.BlockUpdate(data, dataOff, dataLen);
|
|
return mac.DoFinal(macBlock, 0);
|
|
}
|
|
|
|
private int GetAssociatedTextLength()
|
|
{
|
|
return (int)associatedText.Length + ((initialAssociatedText != null) ? initialAssociatedText.Length : 0);
|
|
}
|
|
|
|
private bool HasAssociatedText()
|
|
{
|
|
return GetAssociatedTextLength() > 0;
|
|
}
|
|
}
|