<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />

GcmSivBlockCipher

using Org.BouncyCastle.Crypto.Modes.Gcm; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.IO; using System; using System.IO; namespace Org.BouncyCastle.Crypto.Modes { public class GcmSivBlockCipher : IAeadBlockCipher, IAeadCipher { private class GcmSivCache : MemoryStream { internal GcmSivCache() { } } private class GcmSivHasher { private readonly byte[] theBuffer = new byte[BUFLEN]; private readonly byte[] theByte = new byte[1]; private int numActive; private ulong numHashed; private readonly GcmSivBlockCipher parent; internal GcmSivHasher(GcmSivBlockCipher parent) { this.parent = parent; } internal ulong getBytesProcessed() { return numHashed; } internal void Reset() { numActive = 0; numHashed = 0; } internal void UpdateHash(byte pByte) { theByte[0] = pByte; UpdateHash(theByte, 0, 1); } internal void UpdateHash(byte[] pBuffer, int pOffset, int pLen) { int num = BUFLEN - numActive; int num2 = 0; int num3 = pLen; if (numActive > 0 && pLen >= num) { Array.Copy(pBuffer, pOffset, theBuffer, numActive, num); fillReverse(theBuffer, 0, BUFLEN, parent.theReverse); parent.gHASH(parent.theReverse); num2 += num; num3 -= num; numActive = 0; } while (num3 >= BUFLEN) { fillReverse(pBuffer, pOffset + num2, BUFLEN, parent.theReverse); parent.gHASH(parent.theReverse); num2 += BUFLEN; num3 -= BUFLEN; } if (num3 > 0) { Array.Copy(pBuffer, pOffset + num2, theBuffer, numActive, num3); numActive += num3; } numHashed += (ulong)pLen; } internal void completeHash() { if (numActive > 0) { Arrays.Fill(parent.theReverse, 0); fillReverse(theBuffer, 0, numActive, parent.theReverse); parent.gHASH(parent.theReverse); } } } private static readonly int BUFLEN = 16; private static readonly int HALFBUFLEN = BUFLEN >> 1; private static readonly int NONCELEN = 12; private static readonly int MAX_DATALEN = 2147483639 - BUFLEN; private static readonly byte MASK = 128; private static readonly byte ADD = 225; private static readonly int INIT = 1; private static readonly int AEAD_COMPLETE = 2; private readonly IBlockCipher theCipher; private readonly IGcmMultiplier theMultiplier; internal readonly byte[] theGHash = new byte[BUFLEN]; internal readonly byte[] theReverse = new byte[BUFLEN]; private readonly GcmSivHasher theAEADHasher; private readonly GcmSivHasher theDataHasher; private GcmSivCache thePlain; private GcmSivCache theEncData; private bool forEncryption; private byte[] theInitialAEAD; private byte[] theNonce; private int theFlags; public virtual IBlockCipher UnderlyingCipher => theCipher; public virtual string AlgorithmName => theCipher.AlgorithmName + "-GCM-SIV"; public GcmSivBlockCipher() : this(AesUtilities.CreateEngine()) { } public GcmSivBlockCipher(IBlockCipher pCipher) : this(pCipher, null) { } [Obsolete("Will be removed")] public GcmSivBlockCipher(IBlockCipher pCipher, IGcmMultiplier pMultiplier) { if (pCipher.GetBlockSize() != BUFLEN) throw new ArgumentException("Cipher required with a block size of " + BUFLEN.ToString() + "."); if (pMultiplier == null) pMultiplier = GcmBlockCipher.CreateGcmMultiplier(); theCipher = pCipher; theMultiplier = pMultiplier; theAEADHasher = new GcmSivHasher(this); theDataHasher = new GcmSivHasher(this); } public virtual int GetBlockSize() { return theCipher.GetBlockSize(); } public virtual void Init(bool pEncrypt, ICipherParameters cipherParameters) { byte[] array = null; AeadParameters aeadParameters = cipherParameters as AeadParameters; byte[] array2; KeyParameter keyParameter; if (aeadParameters != null) { array = aeadParameters.GetAssociatedText(); array2 = aeadParameters.GetNonce(); keyParameter = aeadParameters.Key; } else { ParametersWithIV parametersWithIV = cipherParameters as ParametersWithIV; if (parametersWithIV == null) throw new ArgumentException("invalid parameters passed to GCM_SIV"); array2 = parametersWithIV.GetIV(); keyParameter = (KeyParameter)parametersWithIV.Parameters; } if (array2.Length != NONCELEN) throw new ArgumentException("Invalid nonce"); if (keyParameter == null) throw new ArgumentException("Invalid key"); int keyLength = keyParameter.KeyLength; if (keyLength != BUFLEN && keyLength != BUFLEN << 1) throw new ArgumentException("Invalid key"); forEncryption = pEncrypt; theInitialAEAD = array; theNonce = array2; DeriveKeys(keyParameter); ResetStreams(); } private void CheckAeadStatus(int pLen) { if ((theFlags & INIT) == 0) throw new InvalidOperationException("Cipher is not initialised"); if ((theFlags & AEAD_COMPLETE) != 0) throw new InvalidOperationException("AEAD data cannot be processed after ordinary data"); if ((long)theAEADHasher.getBytesProcessed() + -9223372036854775808 > MAX_DATALEN - pLen + -9223372036854775808) throw new InvalidOperationException("AEAD byte count exceeded"); } private void CheckStatus(int pLen) { if ((theFlags & INIT) == 0) throw new InvalidOperationException("Cipher is not initialised"); if ((theFlags & AEAD_COMPLETE) == 0) { theAEADHasher.completeHash(); theFlags |= AEAD_COMPLETE; } long num = MAX_DATALEN; long length = thePlain.Length; if (!forEncryption) { num += BUFLEN; length = theEncData.Length; } if (length + -9223372036854775808 > num - pLen + -9223372036854775808) throw new InvalidOperationException("byte count exceeded"); } public virtual void ProcessAadByte(byte pByte) { CheckAeadStatus(1); theAEADHasher.UpdateHash(pByte); } public virtual void ProcessAadBytes(byte[] pData, int pOffset, int pLen) { Check.DataLength(pData, pOffset, pLen, "input buffer too short"); CheckAeadStatus(pLen); theAEADHasher.UpdateHash(pData, pOffset, pLen); } public virtual int ProcessByte(byte pByte, byte[] pOutput, int pOutOffset) { CheckStatus(1); if (forEncryption) { thePlain.WriteByte(pByte); theDataHasher.UpdateHash(pByte); } else theEncData.WriteByte(pByte); return 0; } public virtual int ProcessBytes(byte[] pData, int pOffset, int pLen, byte[] pOutput, int pOutOffset) { Check.DataLength(pData, pOffset, pLen, "input buffer too short"); CheckStatus(pLen); if (forEncryption) { thePlain.Write(pData, pOffset, pLen); theDataHasher.UpdateHash(pData, pOffset, pLen); } else theEncData.Write(pData, pOffset, pLen); return 0; } public virtual int DoFinal(byte[] pOutput, int pOffset) { Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short"); CheckStatus(0); if (forEncryption) { byte[] array = CalculateTag(); int result = BUFLEN + EncryptPlain(array, pOutput, pOffset); Array.Copy(array, 0, pOutput, pOffset + Convert.ToInt32(thePlain.Length), BUFLEN); ResetStreams(); return result; } DecryptPlain(); int result2 = Streams.WriteBufTo(thePlain, pOutput, pOffset); ResetStreams(); return result2; } public virtual byte[] GetMac() { throw new InvalidOperationException(); } public virtual int GetUpdateOutputSize(int pLen) { return 0; } public virtual int GetOutputSize(int pLen) { if (forEncryption) return pLen + Convert.ToInt32(thePlain.Length) + BUFLEN; int num = pLen + Convert.ToInt32(theEncData.Length); if (num <= BUFLEN) return 0; return num - BUFLEN; } public virtual void Reset() { ResetStreams(); } private void ResetStreams() { if (thePlain != null) { int length = Convert.ToInt32(thePlain.Length); Array.Clear(thePlain.GetBuffer(), 0, length); thePlain.SetLength(0); } theAEADHasher.Reset(); theDataHasher.Reset(); thePlain = new GcmSivCache(); theEncData = (forEncryption ? null : new GcmSivCache()); theFlags &= ~AEAD_COMPLETE; Arrays.Fill(theGHash, 0); if (theInitialAEAD != null) theAEADHasher.UpdateHash(theInitialAEAD, 0, theInitialAEAD.Length); } private static int bufLength(byte[] pBuffer) { if (pBuffer != null) return pBuffer.Length; return 0; } private int EncryptPlain(byte[] pCounter, byte[] pTarget, int pOffset) { byte[] buffer = thePlain.GetBuffer(); int num = Convert.ToInt32(thePlain.Length); byte[] pRight = buffer; byte[] array = Arrays.Clone(pCounter); array[BUFLEN - 1] |= MASK; byte[] array2 = new byte[BUFLEN]; long num2 = num; int num3 = 0; while (num2 > 0) { theCipher.ProcessBlock(array, 0, array2, 0); int num4 = (int)System.Math.Min(BUFLEN, num2); xorBlock(array2, pRight, num3, num4); Array.Copy(array2, 0, pTarget, pOffset + num3, num4); num2 -= num4; num3 += num4; incrementCounter(array); } return num; } private void DecryptPlain() { byte[] buffer = theEncData.GetBuffer(); int num = Convert.ToInt32(theEncData.Length); byte[] array = buffer; int num2 = num - BUFLEN; if (num2 < 0) throw new InvalidCipherTextException("Data too short"); byte[] array2 = Arrays.CopyOfRange(array, num2, num2 + BUFLEN); byte[] array3 = Arrays.Clone(array2); array3[BUFLEN - 1] |= MASK; byte[] array4 = new byte[BUFLEN]; int num3 = 0; while (num2 > 0) { theCipher.ProcessBlock(array3, 0, array4, 0); int num4 = System.Math.Min(BUFLEN, num2); xorBlock(array4, array, num3, num4); thePlain.Write(array4, 0, num4); theDataHasher.UpdateHash(array4, 0, num4); num2 -= num4; num3 += num4; incrementCounter(array3); } if (!Arrays.FixedTimeEquals(CalculateTag(), array2)) { Reset(); throw new InvalidCipherTextException("mac check failed"); } } private byte[] CalculateTag() { theDataHasher.completeHash(); byte[] array = completePolyVal(); byte[] array2 = new byte[BUFLEN]; for (int i = 0; i < NONCELEN; i++) { array[i] ^= theNonce[i]; } array[BUFLEN - 1] &= (byte)(MASK - 1); theCipher.ProcessBlock(array, 0, array2, 0); return array2; } private byte[] completePolyVal() { byte[] array = new byte[BUFLEN]; gHashLengths(); fillReverse(theGHash, 0, BUFLEN, array); return array; } private void gHashLengths() { byte[] array = new byte[BUFLEN]; Pack.UInt64_To_BE(8 * theDataHasher.getBytesProcessed(), array, 0); Pack.UInt64_To_BE(8 * theAEADHasher.getBytesProcessed(), array, 8); gHASH(array); } private void gHASH(byte[] pNext) { xorBlock(theGHash, pNext); theMultiplier.MultiplyH(theGHash); } private static void fillReverse(byte[] pInput, int pOffset, int pLength, byte[] pOutput) { int num = 0; int num2 = BUFLEN - 1; while (num < pLength) { pOutput[num2] = pInput[pOffset + num]; num++; num2--; } } private static void xorBlock(byte[] pLeft, byte[] pRight) { for (int i = 0; i < BUFLEN; i++) { pLeft[i] ^= pRight[i]; } } private static void xorBlock(byte[] pLeft, byte[] pRight, int pOffset, int pLength) { for (int i = 0; i < pLength; i++) { pLeft[i] ^= pRight[i + pOffset]; } } private static void incrementCounter(byte[] pCounter) { for (int i = 0; i < 4; i++) { if (++pCounter[i] != 0) break; } } private static void mulX(byte[] pValue) { byte b = 0; for (int i = 0; i < BUFLEN; i++) { byte b2 = pValue[i]; pValue[i] = (byte)(((b2 >> 1) & ~MASK) | b); b = (byte)(((b2 & 1) != 0) ? MASK : 0); } if (b != 0) pValue[0] ^= ADD; } private void DeriveKeys(KeyParameter pKey) { byte[] array = new byte[BUFLEN]; byte[] array2 = new byte[BUFLEN]; byte[] array3 = new byte[BUFLEN]; byte[] array4 = new byte[pKey.KeyLength]; Array.Copy(theNonce, 0, array, BUFLEN - NONCELEN, NONCELEN); theCipher.Init(true, pKey); int num = 0; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array3, num, HALFBUFLEN); array[0]++; num += HALFBUFLEN; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array3, num, HALFBUFLEN); array[0]++; num = 0; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array4, num, HALFBUFLEN); array[0]++; num += HALFBUFLEN; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array4, num, HALFBUFLEN); if (array4.Length == BUFLEN << 1) { array[0]++; num += HALFBUFLEN; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array4, num, HALFBUFLEN); array[0]++; num += HALFBUFLEN; theCipher.ProcessBlock(array, 0, array2, 0); Array.Copy(array2, 0, array4, num, HALFBUFLEN); } theCipher.Init(true, new KeyParameter(array4)); fillReverse(array3, 0, BUFLEN, array2); mulX(array2); theMultiplier.Init(array2); theFlags |= INIT; } } }