ChaCha20Poly1305
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
using System;
namespace Org.BouncyCastle.Crypto.Modes
{
public class ChaCha20Poly1305 : IAeadCipher
{
private enum State
{
Uninitialized,
EncInit,
EncAad,
EncData,
EncFinal,
DecInit,
DecAad,
DecData,
DecFinal
}
private const int BufSize = 64;
private const int KeySize = 32;
private const int NonceSize = 12;
private const int MacSize = 16;
private static readonly byte[] Zeroes = new byte[15];
private const ulong AadLimit = ulong.MaxValue;
private const ulong DataLimit = 274877906880;
private readonly ChaCha7539Engine mChacha20;
private readonly IMac mPoly1305;
private readonly byte[] mKey = new byte[32];
private readonly byte[] mNonce = new byte[12];
private readonly byte[] mBuf = new byte[80];
private readonly byte[] mMac = new byte[16];
private byte[] mInitialAad;
private ulong mAadCount;
private ulong mDataCount;
private State mState;
private int mBufPos;
public virtual string AlgorithmName => "ChaCha20Poly1305";
public ChaCha20Poly1305()
: this(new Poly1305())
{
}
public ChaCha20Poly1305(IMac poly1305)
{
if (poly1305 == null)
throw new ArgumentNullException("poly1305");
if (16 != poly1305.GetMacSize())
throw new ArgumentException("must be a 128-bit MAC", "poly1305");
mChacha20 = new ChaCha7539Engine();
mPoly1305 = poly1305;
}
public virtual void Init(bool forEncryption, ICipherParameters parameters)
{
AeadParameters aeadParameters = parameters as AeadParameters;
KeyParameter keyParameter;
byte[] array;
ICipherParameters parameters2;
if (aeadParameters != null) {
int macSize = aeadParameters.MacSize;
if (128 != macSize)
throw new ArgumentException("Invalid value for MAC size: " + macSize.ToString());
keyParameter = aeadParameters.Key;
array = aeadParameters.GetNonce();
parameters2 = new ParametersWithIV(keyParameter, array);
mInitialAad = aeadParameters.GetAssociatedText();
} else {
ParametersWithIV parametersWithIV = parameters as ParametersWithIV;
if (parametersWithIV == null)
throw new ArgumentException("invalid parameters passed to ChaCha20Poly1305", "parameters");
keyParameter = (KeyParameter)parametersWithIV.Parameters;
array = parametersWithIV.GetIV();
parameters2 = parametersWithIV;
mInitialAad = null;
}
if (keyParameter == null) {
if (mState == State.Uninitialized)
throw new ArgumentException("Key must be specified in initial init");
} else if (32 != keyParameter.KeyLength) {
throw new ArgumentException("Key must be 256 bits");
}
if (12 != array.Length)
throw new ArgumentException("Nonce must be 96 bits");
if (((mState != State.Uninitialized) & forEncryption) && Arrays.AreEqual(mNonce, array) && (keyParameter == null || keyParameter.FixedTimeEquals(mKey)))
throw new ArgumentException("cannot reuse nonce for ChaCha20Poly1305 encryption");
keyParameter?.CopyTo(mKey, 0, 32);
Array.Copy(array, 0, mNonce, 0, 12);
mChacha20.Init(true, parameters2);
mState = (forEncryption ? State.EncInit : State.DecInit);
Reset(true, false);
}
public virtual int GetOutputSize(int len)
{
int num = System.Math.Max(0, len);
switch (mState) {
case State.DecInit:
case State.DecAad:
return System.Math.Max(0, num - 16);
case State.DecData:
case State.DecFinal:
return System.Math.Max(0, num + mBufPos - 16);
case State.EncData:
case State.EncFinal:
return num + mBufPos + 16;
default:
return num + 16;
}
}
public virtual int GetUpdateOutputSize(int len)
{
int num = System.Math.Max(0, len);
switch (mState) {
case State.DecInit:
case State.DecAad:
num = System.Math.Max(0, num - 16);
break;
case State.DecData:
case State.DecFinal:
num = System.Math.Max(0, num + mBufPos - 16);
break;
case State.EncData:
case State.EncFinal:
num += mBufPos;
break;
}
return num - num % 64;
}
public virtual void ProcessAadByte(byte input)
{
CheckAad();
mAadCount = IncrementCount(mAadCount, 1, ulong.MaxValue);
mPoly1305.Update(input);
}
public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
{
if (inBytes == null)
throw new ArgumentNullException("inBytes");
if (inOff < 0)
throw new ArgumentException("cannot be negative", "inOff");
if (len < 0)
throw new ArgumentException("cannot be negative", "len");
Check.DataLength(inBytes, inOff, len, "input buffer too short");
CheckAad();
if (len > 0) {
mAadCount = IncrementCount(mAadCount, (uint)len, ulong.MaxValue);
mPoly1305.BlockUpdate(inBytes, inOff, len);
}
}
public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
{
CheckData();
switch (mState) {
case State.DecData:
mBuf[mBufPos] = input;
if (++mBufPos == mBuf.Length) {
mPoly1305.BlockUpdate(mBuf, 0, 64);
ProcessBlock(mBuf, 0, outBytes, outOff);
Array.Copy(mBuf, 64, mBuf, 0, 16);
mBufPos = 16;
return 64;
}
return 0;
case State.EncData:
mBuf[mBufPos] = input;
if (++mBufPos == 64) {
ProcessBlock(mBuf, 0, outBytes, outOff);
mPoly1305.BlockUpdate(outBytes, outOff, 64);
mBufPos = 0;
return 64;
}
return 0;
default:
throw new InvalidOperationException();
}
}
public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
{
if (inBytes == null)
throw new ArgumentNullException("inBytes");
if (inOff < 0)
throw new ArgumentException("cannot be negative", "inOff");
if (len < 0)
throw new ArgumentException("cannot be negative", "len");
Check.DataLength(inBytes, inOff, len, "input buffer too short");
if (outOff < 0)
throw new ArgumentException("cannot be negative", "outOff");
CheckData();
int num = 0;
switch (mState) {
case State.DecData: {
int num5 = mBuf.Length - mBufPos;
if (len < num5) {
Array.Copy(inBytes, inOff, mBuf, mBufPos, len);
mBufPos += len;
} else {
if (mBufPos >= 64) {
mPoly1305.BlockUpdate(mBuf, 0, 64);
ProcessBlock(mBuf, 0, outBytes, outOff);
Array.Copy(mBuf, 64, mBuf, 0, mBufPos -= 64);
num = 64;
num5 += 64;
if (len < num5) {
Array.Copy(inBytes, inOff, mBuf, mBufPos, len);
mBufPos += len;
break;
}
}
int num6 = inOff + len - mBuf.Length;
int num7 = num6 - 64;
num5 = 64 - mBufPos;
Array.Copy(inBytes, inOff, mBuf, mBufPos, num5);
mPoly1305.BlockUpdate(mBuf, 0, 64);
ProcessBlock(mBuf, 0, outBytes, outOff + num);
inOff += num5;
num += 64;
while (inOff <= num7) {
mPoly1305.BlockUpdate(inBytes, inOff, 128);
ProcessBlocks2(inBytes, inOff, outBytes, outOff + num);
inOff += 128;
num += 128;
}
if (inOff <= num6) {
mPoly1305.BlockUpdate(inBytes, inOff, 64);
ProcessBlock(inBytes, inOff, outBytes, outOff + num);
inOff += 64;
num += 64;
}
mBufPos = mBuf.Length + num6 - inOff;
Array.Copy(inBytes, inOff, mBuf, 0, mBufPos);
}
break;
}
case State.EncData: {
int num2 = 64 - mBufPos;
if (len < num2) {
Array.Copy(inBytes, inOff, mBuf, mBufPos, len);
mBufPos += len;
} else {
int num3 = inOff + len - 64;
int num4 = num3 - 64;
if (mBufPos > 0) {
Array.Copy(inBytes, inOff, mBuf, mBufPos, num2);
ProcessBlock(mBuf, 0, outBytes, outOff);
inOff += num2;
num = 64;
}
while (inOff <= num4) {
ProcessBlocks2(inBytes, inOff, outBytes, outOff + num);
inOff += 128;
num += 128;
}
if (inOff <= num3) {
ProcessBlock(inBytes, inOff, outBytes, outOff + num);
inOff += 64;
num += 64;
}
mPoly1305.BlockUpdate(outBytes, outOff, num);
mBufPos = 64 + num3 - inOff;
Array.Copy(inBytes, inOff, mBuf, 0, mBufPos);
}
break;
}
default:
throw new InvalidOperationException();
}
return num;
}
public virtual int DoFinal(byte[] outBytes, int outOff)
{
if (outBytes == null)
throw new ArgumentNullException("outBytes");
if (outOff < 0)
throw new ArgumentException("cannot be negative", "outOff");
CheckData();
Array.Clear(mMac, 0, 16);
int num = 0;
switch (mState) {
case State.DecData:
if (mBufPos < 16)
throw new InvalidCipherTextException("data too short");
num = mBufPos - 16;
Check.OutputLength(outBytes, outOff, num, "output buffer too short");
if (num > 0) {
mPoly1305.BlockUpdate(mBuf, 0, num);
ProcessData(mBuf, 0, num, outBytes, outOff);
}
FinishData(State.DecFinal);
if (!Arrays.FixedTimeEquals(16, mMac, 0, mBuf, num))
throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
break;
case State.EncData:
num = mBufPos + 16;
Check.OutputLength(outBytes, outOff, num, "output buffer too short");
if (mBufPos > 0) {
ProcessData(mBuf, 0, mBufPos, outBytes, outOff);
mPoly1305.BlockUpdate(outBytes, outOff, mBufPos);
}
FinishData(State.EncFinal);
Array.Copy(mMac, 0, outBytes, outOff + mBufPos, 16);
break;
default:
throw new InvalidOperationException();
}
Reset(false, true);
return num;
}
public virtual byte[] GetMac()
{
return Arrays.Clone(mMac);
}
public virtual void Reset()
{
Reset(true, true);
}
private void CheckAad()
{
switch (mState) {
case State.EncAad:
case State.DecAad:
break;
case State.DecInit:
mState = State.DecAad;
break;
case State.EncInit:
mState = State.EncAad;
break;
case State.EncFinal:
throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption");
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
}
}
private void CheckData()
{
switch (mState) {
case State.EncData:
case State.DecData:
break;
case State.DecInit:
case State.DecAad:
FinishAad(State.DecData);
break;
case State.EncInit:
case State.EncAad:
FinishAad(State.EncData);
break;
case State.EncFinal:
throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption");
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
}
}
private void FinishAad(State nextState)
{
PadMac(mAadCount);
mState = nextState;
}
private void FinishData(State nextState)
{
PadMac(mDataCount);
byte[] array = new byte[16];
Pack.UInt64_To_LE(mAadCount, array, 0);
Pack.UInt64_To_LE(mDataCount, array, 8);
mPoly1305.BlockUpdate(array, 0, 16);
mPoly1305.DoFinal(mMac, 0);
mState = nextState;
}
private ulong IncrementCount(ulong count, uint increment, ulong limit)
{
if (count > limit - increment)
throw new InvalidOperationException("Limit exceeded");
return count + increment;
}
private void InitMac()
{
byte[] array = new byte[64];
try {
mChacha20.ProcessBytes(array, 0, 64, array, 0);
mPoly1305.Init(new KeyParameter(array, 0, 32));
} finally {
Array.Clear(array, 0, 64);
}
}
private void PadMac(ulong count)
{
int num = (int)count & 15;
if (num != 0)
mPoly1305.BlockUpdate(Zeroes, 0, 16 - num);
}
private void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
{
Check.OutputLength(outBytes, outOff, 64, "output buffer too short");
mChacha20.ProcessBlock(inBytes, inOff, outBytes, outOff);
mDataCount = IncrementCount(mDataCount, 64, 274877906880);
}
private void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
{
Check.OutputLength(outBytes, outOff, 128, "output buffer too short");
mChacha20.ProcessBlocks2(inBytes, inOff, outBytes, outOff);
mDataCount = IncrementCount(mDataCount, 128, 274877906880);
}
private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
{
Check.OutputLength(outBytes, outOff, inLen, "output buffer too short");
mChacha20.ProcessBytes(inBytes, inOff, inLen, outBytes, outOff);
mDataCount = IncrementCount(mDataCount, (uint)inLen, 274877906880);
}
private void Reset(bool clearMac, bool resetCipher)
{
Array.Clear(mBuf, 0, mBuf.Length);
if (clearMac)
Array.Clear(mMac, 0, mMac.Length);
mAadCount = 0;
mDataCount = 0;
mBufPos = 0;
switch (mState) {
case State.DecAad:
case State.DecData:
case State.DecFinal:
mState = State.DecInit;
break;
case State.EncAad:
case State.EncData:
case State.EncFinal:
mState = State.EncFinal;
return;
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
case State.EncInit:
case State.DecInit:
break;
}
if (resetCipher)
mChacha20.Reset();
InitMac();
if (mInitialAad != null)
ProcessAadBytes(mInitialAad, 0, mInitialAad.Length);
}
}
}