<PackageReference Include="BouncyCastle.Cryptography" Version="2.7.0-beta.98" />

CcmBlockCipher

using Org.BouncyCastle.Crypto.Macs; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Utilities; using System; using System.IO; namespace Org.BouncyCastle.Crypto.Modes { public class CcmBlockCipher : IAeadBlockCipher, IAeadCipher { 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 IBlockCipher UnderlyingCipher => cipher; 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.ToString() + "."); } public virtual void Init(bool forEncryption, ICipherParameters parameters) { this.forEncryption = forEncryption; AeadParameters aeadParameters = parameters as AeadParameters; ICipherParameters cipherParameters; if (aeadParameters != null) { nonce = aeadParameters.GetNonce(); initialAssociatedText = aeadParameters.GetAssociatedText(); macSize = GetMacSize(forEncryption, aeadParameters.MacSize); cipherParameters = aeadParameters.Key; } else { ParametersWithIV parametersWithIV = parameters as ParametersWithIV; if (parametersWithIV == null) throw new ArgumentException("invalid parameters passed to CCM"); nonce = parametersWithIV.GetIV(); initialAssociatedText = null; macSize = GetMacSize(forEncryption, 64); cipherParameters = parametersWithIV.Parameters; } if (cipherParameters != null) keyParam = cipherParameters; if (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 void ProcessAadBytes(ReadOnlySpan<byte> input) { associatedText.Write(input); } public virtual int ProcessByte(byte input, byte[] outBytes, int outOff) { data.WriteByte(input); return 0; } public virtual int ProcessByte(byte input, Span<byte> output) { 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 ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output) { data.Write(input); return 0; } public virtual int DoFinal(byte[] outBytes, int outOff) { return DoFinal(outBytes.AsSpan(outOff)); } public virtual int DoFinal(Span<byte> output) { if (!data.TryGetBuffer(out ArraySegment<byte> buffer)) throw new UnauthorizedAccessException(); int result = ProcessPacket(buffer, output); Reset(); return result; } public virtual void Reset() { associatedText.SetLength(0); data.SetLength(0); } 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 = Convert.ToInt32(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) { Check.DataLength(input, inOff, inLen, "input buffer too short"); 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) { Check.DataLength(input, inOff, inLen, "input buffer too short"); return ProcessPacket(input.AsSpan(inOff, inLen), output.AsSpan(outOff)); } public unsafe virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output) { int length = input.Length; 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; int num4 = 0; if (!forEncryption) num4 = 16; if (length - num4 >= 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); SicBlockCipher sicBlockCipher = new SicBlockCipher(cipher); sicBlockCipher.Init(forEncryption, new ParametersWithIV(keyParam, array)); int i = 0; int blockSize = BlockSize; Span<byte> span = new Span<byte>(stackalloc byte[(int)(uint)blockSize], blockSize); int num5; ReadOnlySpan<byte> readOnlySpan; Span<byte> span2; if (forEncryption) { num5 = length + macSize; Check.OutputLength(output, num5, "output buffer too short"); CalculateMac(input, macBlock); byte[] array2 = new byte[BlockSize]; sicBlockCipher.ProcessBlock(macBlock, array2); for (; i < length - BlockSize; i += BlockSize) { SicBlockCipher sicBlockCipher2 = sicBlockCipher; blockSize = i; ReadOnlySpan<byte> input2 = input.Slice(blockSize, input.Length - blockSize); blockSize = i; sicBlockCipher2.ProcessBlock(input2, output.Slice(blockSize, output.Length - blockSize)); } blockSize = i; readOnlySpan = input.Slice(blockSize, input.Length - blockSize); readOnlySpan.CopyTo(span); sicBlockCipher.ProcessBlock(span, span); span2 = span.Slice(0, length - i); blockSize = i; span2.CopyTo(output.Slice(blockSize, output.Length - blockSize)); span2 = array2.AsSpan(0, macSize); blockSize = length; span2.CopyTo(output.Slice(blockSize, output.Length - blockSize)); } else { if (length < macSize) throw new InvalidCipherTextException("data too short"); num5 = length - macSize; Check.OutputLength(output, num5, "output buffer too short"); blockSize = num5; readOnlySpan = input.Slice(blockSize, input.Length - blockSize); readOnlySpan.CopyTo(macBlock); sicBlockCipher.ProcessBlock(macBlock, macBlock); for (int j = macSize; j != macBlock.Length; j++) { macBlock[j] = 0; } for (; i < num5 - BlockSize; i += BlockSize) { SicBlockCipher sicBlockCipher3 = sicBlockCipher; blockSize = i; ReadOnlySpan<byte> input3 = input.Slice(blockSize, input.Length - blockSize); blockSize = i; sicBlockCipher3.ProcessBlock(input3, output.Slice(blockSize, output.Length - blockSize)); } blockSize = i; readOnlySpan = input.Slice(blockSize, num5 - blockSize); readOnlySpan.CopyTo(span); sicBlockCipher.ProcessBlock(span, span); span2 = span.Slice(0, num5 - i); blockSize = i; span2.CopyTo(output.Slice(blockSize, output.Length - blockSize)); blockSize = BlockSize; Span<byte> span3 = new Span<byte>(stackalloc byte[(int)(uint)blockSize], blockSize); CalculateMac(output.Slice(0, num5), span3); if (!Arrays.FixedTimeEquals(macBlock, span3)) throw new InvalidCipherTextException("mac check in CCM failed"); } return num5; } private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) { return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock); } private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock) { CbcBlockCipherMac cbcBlockCipherMac = new CbcBlockCipherMac(cipher, macSize * 8); cbcBlockCipherMac.Init(keyParam); byte[] array = new byte[16]; if (HasAssociatedText()) array[0] |= 64; array[0] |= (byte)((((cbcBlockCipherMac.GetMacSize() - 2) / 2) & 7) << 3); array[0] |= (byte)((15 - nonce.Length - 1) & 7); Array.Copy(nonce, 0, array, 1, nonce.Length); int num = data.Length; int num2 = 1; while (num > 0) { array[array.Length - num2] = (byte)(num & 255); num >>= 8; num2++; } cbcBlockCipherMac.BlockUpdate(array, 0, array.Length); if (HasAssociatedText()) { int associatedTextLength = GetAssociatedTextLength(); int num3; if (associatedTextLength < 65280) { cbcBlockCipherMac.Update((byte)(associatedTextLength >> 8)); cbcBlockCipherMac.Update((byte)associatedTextLength); num3 = 2; } else { cbcBlockCipherMac.Update(byte.MaxValue); cbcBlockCipherMac.Update(254); cbcBlockCipherMac.Update((byte)(associatedTextLength >> 24)); cbcBlockCipherMac.Update((byte)(associatedTextLength >> 16)); cbcBlockCipherMac.Update((byte)(associatedTextLength >> 8)); cbcBlockCipherMac.Update((byte)associatedTextLength); num3 = 6; } if (initialAssociatedText != null) cbcBlockCipherMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length); if (associatedText.Length > 0) { byte[] buffer = associatedText.GetBuffer(); int len = Convert.ToInt32(associatedText.Length); cbcBlockCipherMac.BlockUpdate(buffer, 0, len); } num3 = (num3 + associatedTextLength) % 16; if (num3 != 0) { for (int i = num3; i < 16; i++) { cbcBlockCipherMac.Update(0); } } } cbcBlockCipherMac.BlockUpdate(data); return cbcBlockCipherMac.DoFinal(macBlock); } private int GetMacSize(bool forEncryption, int requestedMacBits) { if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || (requestedMacBits & 15) != 0)) throw new ArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}"); return requestedMacBits >> 3; } private int GetAssociatedTextLength() { return Convert.ToInt32(associatedText.Length) + ((initialAssociatedText != null) ? initialAssociatedText.Length : 0); } private bool HasAssociatedText() { return GetAssociatedTextLength() > 0; } } }