GcmSivBlockCipher
using Org.BouncyCastle.Crypto.Modes.Gcm;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
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 UpdateHash(ReadOnlySpan<byte> buffer)
{
int length = buffer.Length;
int num = BUFLEN - numActive;
if (numActive > 0 && buffer.Length >= num) {
buffer.Slice(0, num).CopyTo(theBuffer.AsSpan(numActive));
fillReverse(theBuffer, parent.theReverse);
parent.gHASH(parent.theReverse);
int num2 = num;
buffer = buffer.Slice(num2, buffer.Length - num2);
numActive = 0;
}
while (buffer.Length >= BUFLEN) {
fillReverse(buffer.Slice(0, BUFLEN), parent.theReverse);
parent.gHASH(parent.theReverse);
int num2 = BUFLEN;
buffer = buffer.Slice(num2, buffer.Length - num2);
}
if (!buffer.IsEmpty) {
buffer.CopyTo(theBuffer.AsSpan(numActive));
numActive += buffer.Length;
}
numHashed += (ulong)length;
}
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;
ReadOnlySpan<byte> readOnlySpan;
KeyParameter keyParameter;
if (aeadParameters != null) {
array = aeadParameters.GetAssociatedText();
readOnlySpan = aeadParameters.Nonce;
keyParameter = aeadParameters.Key;
} else {
ParametersWithIV parametersWithIV = cipherParameters as ParametersWithIV;
if (parametersWithIV == null)
throw new ArgumentException("invalid parameters passed to GCM_SIV");
readOnlySpan = parametersWithIV.IV;
keyParameter = (KeyParameter)parametersWithIV.Parameters;
}
if (readOnlySpan.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 = readOnlySpan.ToArray();
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");
ProcessAadBytes(pData.AsSpan(pOffset, pLen));
}
public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
{
CheckAeadStatus(input.Length);
theAEADHasher.UpdateHash(input);
}
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 ProcessByte(byte input, Span<byte> output)
{
CheckStatus(1);
if (forEncryption) {
thePlain.WriteByte(input);
theDataHasher.UpdateHash(input);
} else
theEncData.WriteByte(input);
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");
return ProcessBytes(pData.AsSpan(pOffset, pLen), Spans.FromNullable(pOutput, pOutOffset));
}
public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
{
CheckStatus(input.Length);
if (forEncryption) {
thePlain.Write(input);
theDataHasher.UpdateHash(input);
} else
theEncData.Write(input);
return 0;
}
public virtual int DoFinal(byte[] pOutput, int pOffset)
{
Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short");
return DoFinal(pOutput.AsSpan(pOffset));
}
public virtual int DoFinal(Span<byte> output)
{
CheckStatus(0);
Check.OutputLength(output, GetOutputSize(0), "output buffer too short");
Span<byte> span;
if (forEncryption) {
byte[] array = CalculateTag();
int result = BUFLEN + EncryptPlain(array, output);
span = array.AsSpan(0, BUFLEN);
int num = Convert.ToInt32(thePlain.Length);
span.CopyTo(output.Slice(num, output.Length - num));
ResetStreams();
return result;
}
DecryptPlain();
if (!thePlain.TryGetBuffer(out ArraySegment<byte> buffer))
throw new InvalidOperationException();
span = buffer.AsSpan();
span.CopyTo(output);
int count = buffer.Count;
ResetStreams();
return count;
}
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, Span<byte> target)
{
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);
Span<byte> span = array2.AsSpan(0, num4);
int num5 = num3;
span.CopyTo(target.Slice(num5, target.Length - num5));
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 fillReverse(ReadOnlySpan<byte> input, Span<byte> output)
{
int num = 0;
int num2 = BUFLEN - 1;
while (num < input.Length) {
output[num2] = input[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;
}
}
}