diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index e20fa2b63..eeefee157 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -890,11 +890,13 @@
<Compile Include="src\crypto\modes\CbcBlockCipher.cs" />
<Compile Include="src\crypto\modes\CcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\CfbBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\ChaCha20Poly1305.cs" />
<Compile Include="src\crypto\modes\CtsBlockCipher.cs" />
<Compile Include="src\crypto\modes\EAXBlockCipher.cs" />
<Compile Include="src\crypto\modes\GCMBlockCipher.cs" />
<Compile Include="src\crypto\modes\GOFBBlockCipher.cs" />
<Compile Include="src\crypto\modes\IAeadBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\IAeadCipher.cs" />
<Compile Include="src\crypto\modes\KCcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\KCtrBlockCipher.cs" />
<Compile Include="src\crypto\modes\OCBBlockCipher.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index ab8559472..990d69553 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -884,11 +884,13 @@
<Compile Include="src\crypto\modes\CbcBlockCipher.cs" />
<Compile Include="src\crypto\modes\CcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\CfbBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\ChaCha20Poly1305.cs" />
<Compile Include="src\crypto\modes\CtsBlockCipher.cs" />
<Compile Include="src\crypto\modes\EAXBlockCipher.cs" />
<Compile Include="src\crypto\modes\GCMBlockCipher.cs" />
<Compile Include="src\crypto\modes\GOFBBlockCipher.cs" />
<Compile Include="src\crypto\modes\IAeadBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\IAeadCipher.cs" />
<Compile Include="src\crypto\modes\KCcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\KCtrBlockCipher.cs" />
<Compile Include="src\crypto\modes\OCBBlockCipher.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 8bb7fe7b0..118bde49b 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -885,11 +885,13 @@
<Compile Include="src\crypto\modes\CbcBlockCipher.cs" />
<Compile Include="src\crypto\modes\CcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\CfbBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\ChaCha20Poly1305.cs" />
<Compile Include="src\crypto\modes\CtsBlockCipher.cs" />
<Compile Include="src\crypto\modes\EAXBlockCipher.cs" />
<Compile Include="src\crypto\modes\GCMBlockCipher.cs" />
<Compile Include="src\crypto\modes\GOFBBlockCipher.cs" />
<Compile Include="src\crypto\modes\IAeadBlockCipher.cs" />
+ <Compile Include="src\crypto\modes\IAeadCipher.cs" />
<Compile Include="src\crypto\modes\KCcmBlockCipher.cs" />
<Compile Include="src\crypto\modes\KCtrBlockCipher.cs" />
<Compile Include="src\crypto\modes\OCBBlockCipher.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 08e7b0ee1..de2dedf03 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4304,6 +4304,11 @@
BuildAction = "Compile"
/>
<File
+ RelPath = "src\crypto\modes\ChaCha20Poly1305.cs"
+ SubType = "Code"
+ BuildAction = "Compile"
+ />
+ <File
RelPath = "src\crypto\modes\CTSBlockCipher.cs"
SubType = "Code"
BuildAction = "Compile"
@@ -4329,6 +4334,11 @@
BuildAction = "Compile"
/>
<File
+ RelPath = "src\crypto\modes\IAeadCipher.cs"
+ SubType = "Code"
+ BuildAction = "Compile"
+ />
+ <File
RelPath = "src\crypto\modes\KCcmBlockCipher.cs"
SubType = "Code"
BuildAction = "Compile"
@@ -12148,6 +12158,11 @@
BuildAction = "Compile"
/>
<File
+ RelPath = "test\src\crypto\test\ChaCha20Poly1305Test.cs"
+ SubType = "Code"
+ BuildAction = "Compile"
+ />
+ <File
RelPath = "test\src\crypto\test\ChaChaTest.cs"
SubType = "Code"
BuildAction = "Compile"
diff --git a/crypto/src/crypto/modes/ChaCha20Poly1305.cs b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
new file mode 100644
index 000000000..9cc62174c
--- /dev/null
+++ b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
@@ -0,0 +1,551 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+ public class ChaCha20Poly1305
+ : IAeadCipher
+ {
+ private enum State
+ {
+ Uninitialized = 0,
+ EncInit = 1,
+ EncAad = 2,
+ EncData = 3,
+ EncFinal = 4,
+ DecInit = 5,
+ DecAad = 6,
+ DecData = 7,
+ DecFinal = 8,
+ }
+
+ 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[MacSize - 1];
+
+ private const ulong AadLimit = ulong.MaxValue;
+ private const ulong DataLimit = ((1UL << 32) - 1) * 64;
+
+ private readonly ChaCha7539Engine mChacha20;
+ private readonly IMac mPoly1305;
+
+ private readonly byte[] mKey = new byte[KeySize];
+ private readonly byte[] mNonce = new byte[NonceSize];
+ private readonly byte[] mBuf = new byte[BufSize + MacSize];
+ private readonly byte[] mMac = new byte[MacSize];
+
+ private byte[] mInitialAad;
+
+ private ulong mAadCount;
+ private ulong mDataCount;
+ private State mState = State.Uninitialized;
+ private int mBufPos;
+
+ public ChaCha20Poly1305()
+ : this(new Poly1305())
+ {
+ }
+
+ public ChaCha20Poly1305(IMac poly1305)
+ {
+ if (null == poly1305)
+ throw new ArgumentNullException("poly1305");
+ if (MacSize != poly1305.GetMacSize())
+ throw new ArgumentException("must be a 128-bit MAC", "poly1305");
+
+ this.mChacha20 = new ChaCha7539Engine();
+ this.mPoly1305 = poly1305;
+ }
+
+ public virtual string AlgorithmName
+ {
+ get { return "ChaCha20Poly1305"; }
+ }
+
+ public virtual void Init(bool forEncryption, ICipherParameters parameters)
+ {
+ KeyParameter initKeyParam;
+ byte[] initNonce;
+ ICipherParameters chacha20Params;
+
+ if (parameters is AeadParameters)
+ {
+ AeadParameters aeadParams = (AeadParameters)parameters;
+
+ int macSizeBits = aeadParams.MacSize;
+ if ((MacSize * 8) != macSizeBits)
+ throw new ArgumentException("Invalid value for MAC size: " + macSizeBits);
+
+ initKeyParam = aeadParams.Key;
+ initNonce = aeadParams.GetNonce();
+ chacha20Params = new ParametersWithIV(initKeyParam, initNonce);
+
+ this.mInitialAad = aeadParams.GetAssociatedText();
+ }
+ else if (parameters is ParametersWithIV)
+ {
+ ParametersWithIV ivParams = (ParametersWithIV)parameters;
+
+ initKeyParam = (KeyParameter)ivParams.Parameters;
+ initNonce = ivParams.GetIV();
+ chacha20Params = ivParams;
+
+ this.mInitialAad = null;
+ }
+ else
+ {
+ throw new ArgumentException("invalid parameters passed to ChaCha20Poly1305", "parameters");
+ }
+
+ // Validate key
+ if (null == initKeyParam)
+ {
+ if (State.Uninitialized == mState)
+ throw new ArgumentException("Key must be specified in initial init");
+ }
+ else
+ {
+ if (KeySize != initKeyParam.GetKey().Length)
+ throw new ArgumentException("Key must be 256 bits");
+ }
+
+ // Validate nonce
+ if (null == initNonce || NonceSize != initNonce.Length)
+ throw new ArgumentException("Nonce must be 96 bits");
+
+ // Check for encryption with reused nonce
+ if (State.Uninitialized != mState && forEncryption && Arrays.AreEqual(mNonce, initNonce))
+ {
+ if (null == initKeyParam || Arrays.AreEqual(mKey, initKeyParam.GetKey()))
+ throw new ArgumentException("cannot reuse nonce for ChaCha20Poly1305 encryption");
+ }
+
+ if (null != initKeyParam)
+ {
+ Array.Copy(initKeyParam.GetKey(), 0, mKey, 0, KeySize);
+ }
+
+ Array.Copy(initNonce, 0, mNonce, 0, NonceSize);
+
+ mChacha20.Init(true, chacha20Params);
+
+ this.mState = forEncryption ? State.EncInit : State.DecInit;
+
+ Reset(true, false);
+ }
+
+ public virtual int GetOutputSize(int len)
+ {
+ int total = System.Math.Max(0, len) + mBufPos;
+
+ switch (mState)
+ {
+ case State.DecInit:
+ case State.DecAad:
+ case State.DecData:
+ return System.Math.Max(0, total - MacSize);
+ case State.EncInit:
+ case State.EncAad:
+ case State.EncData:
+ return total + MacSize;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ public virtual int GetUpdateOutputSize(int len)
+ {
+ int total = System.Math.Max(0, len) + mBufPos;
+
+ switch (mState)
+ {
+ case State.DecInit:
+ case State.DecAad:
+ case State.DecData:
+ total = System.Math.Max(0, total - MacSize);
+ break;
+ case State.EncInit:
+ case State.EncAad:
+ case State.EncData:
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ return total - (total % BufSize);
+ }
+
+ public virtual void ProcessAadByte(byte input)
+ {
+ CheckAad();
+
+ this.mAadCount = IncrementCount(mAadCount, 1, AadLimit);
+ mPoly1305.Update(input);
+ }
+
+ public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
+ {
+ if (null == inBytes)
+ 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)
+ {
+ this.mAadCount = IncrementCount(mAadCount, (uint)len, AadLimit);
+ 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, BufSize);
+ ProcessData(mBuf, 0, BufSize, outBytes, outOff);
+ Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
+ this.mBufPos = MacSize;
+ return BufSize;
+ }
+
+ return 0;
+ }
+ case State.EncData:
+ {
+ mBuf[mBufPos] = input;
+ if (++mBufPos == BufSize)
+ {
+ ProcessData(mBuf, 0, BufSize, outBytes, outOff);
+ mPoly1305.BlockUpdate(outBytes, outOff, BufSize);
+ this.mBufPos = 0;
+ return BufSize;
+ }
+
+ return 0;
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
+ {
+ if (null == inBytes)
+ throw new ArgumentNullException("inBytes");
+ if (null == outBytes)
+ throw new ArgumentNullException("outBytes");
+ 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 resultLen = 0;
+
+ switch (mState)
+ {
+ case State.DecData:
+ {
+ for (int i = 0; i < len; ++i)
+ {
+ mBuf[mBufPos] = inBytes[inOff + i];
+ if (++mBufPos == mBuf.Length)
+ {
+ mPoly1305.BlockUpdate(mBuf, 0, BufSize);
+ ProcessData(mBuf, 0, BufSize, outBytes, outOff + resultLen);
+ Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
+ this.mBufPos = MacSize;
+ resultLen += BufSize;
+ }
+ }
+ break;
+ }
+ case State.EncData:
+ {
+ if (mBufPos != 0)
+ {
+ while (len > 0)
+ {
+ --len;
+ mBuf[mBufPos] = inBytes[inOff++];
+ if (++mBufPos == BufSize)
+ {
+ ProcessData(mBuf, 0, BufSize, outBytes, outOff);
+ mPoly1305.BlockUpdate(outBytes, outOff, BufSize);
+ this.mBufPos = 0;
+ resultLen = BufSize;
+ break;
+ }
+ }
+ }
+
+ while (len >= BufSize)
+ {
+ ProcessData(inBytes, inOff, BufSize, outBytes, outOff + resultLen);
+ mPoly1305.BlockUpdate(outBytes, outOff + resultLen, BufSize);
+ inOff += BufSize;
+ len -= BufSize;
+ resultLen += BufSize;
+ }
+
+ if (len > 0)
+ {
+ Array.Copy(inBytes, inOff, mBuf, 0, len);
+ this.mBufPos = len;
+ }
+ break;
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+
+ return resultLen;
+ }
+
+ public virtual int DoFinal(byte[] outBytes, int outOff)
+ {
+ if (null == outBytes)
+ throw new ArgumentNullException("outBytes");
+ if (outOff < 0)
+ throw new ArgumentException("cannot be negative", "outOff");
+
+ CheckData();
+
+ Array.Clear(mMac, 0, MacSize);
+
+ int resultLen = 0;
+
+ switch (mState)
+ {
+ case State.DecData:
+ {
+ if (mBufPos < MacSize)
+ throw new InvalidCipherTextException("data too short");
+
+ resultLen = mBufPos - MacSize;
+
+ Check.OutputLength(outBytes, outOff, resultLen, "output buffer too short");
+
+ if (resultLen > 0)
+ {
+ mPoly1305.BlockUpdate(mBuf, 0, resultLen);
+ ProcessData(mBuf, 0, resultLen, outBytes, outOff);
+ }
+
+ FinishData(State.DecFinal);
+
+ if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
+ {
+ throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
+ }
+
+ break;
+ }
+ case State.EncData:
+ {
+ resultLen = mBufPos + MacSize;
+
+ Check.OutputLength(outBytes, outOff, resultLen, "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, MacSize);
+ break;
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+
+ Reset(false, true);
+
+ return resultLen;
+ }
+
+ public virtual byte[] GetMac()
+ {
+ return Arrays.Clone(mMac);
+ }
+
+ public virtual void Reset()
+ {
+ Reset(true, true);
+ }
+
+ private void CheckAad()
+ {
+ switch (mState)
+ {
+ case State.DecInit:
+ this.mState = State.DecAad;
+ break;
+ case State.EncInit:
+ this.mState = State.EncAad;
+ break;
+ case State.DecAad:
+ case State.EncAad:
+ break;
+ case State.EncFinal:
+ throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption");
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private void CheckData()
+ {
+ switch (mState)
+ {
+ case State.DecInit:
+ case State.DecAad:
+ FinishAad(State.DecData);
+ break;
+ case State.EncInit:
+ case State.EncAad:
+ FinishAad(State.EncData);
+ break;
+ case State.DecData:
+ case State.EncData:
+ break;
+ case State.EncFinal:
+ throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption");
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ private void FinishAad(State nextState)
+ {
+ PadMac(mAadCount);
+
+ this.mState = nextState;
+ }
+
+ private void FinishData(State nextState)
+ {
+ PadMac(mDataCount);
+
+ byte[] lengths = new byte[16];
+ Pack.UInt64_To_LE(mAadCount, lengths, 0);
+ Pack.UInt64_To_LE(mDataCount, lengths, 8);
+ mPoly1305.BlockUpdate(lengths, 0, 16);
+
+ mPoly1305.DoFinal(mMac, 0);
+
+ this.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[] firstBlock = new byte[64];
+ try
+ {
+ mChacha20.ProcessBytes(firstBlock, 0, 64, firstBlock, 0);
+ mPoly1305.Init(new KeyParameter(firstBlock, 0, 32));
+ }
+ finally
+ {
+ Array.Clear(firstBlock, 0, 64);
+ }
+ }
+
+ private void PadMac(ulong count)
+ {
+ int partial = (int)count % MacSize;
+ if (0 != partial)
+ {
+ mPoly1305.BlockUpdate(Zeroes, 0, MacSize - partial);
+ }
+ }
+
+ 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);
+
+ this.mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit);
+ }
+
+ private void Reset(bool clearMac, bool resetCipher)
+ {
+ Array.Clear(mBuf, 0, mBuf.Length);
+
+ if (clearMac)
+ {
+ Array.Clear(mMac, 0, mMac.Length);
+ }
+
+ this.mAadCount = 0UL;
+ this.mDataCount = 0UL;
+ this.mBufPos = 0;
+
+ switch (mState)
+ {
+ case State.DecInit:
+ case State.EncInit:
+ break;
+ case State.DecAad:
+ case State.DecData:
+ case State.DecFinal:
+ this.mState = State.DecInit;
+ break;
+ case State.EncAad:
+ case State.EncData:
+ case State.EncFinal:
+ this.mState = State.EncFinal;
+ return;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ if (resetCipher)
+ {
+ mChacha20.Reset();
+ }
+
+ InitMac();
+
+ if (null != mInitialAad)
+ {
+ ProcessAadBytes(mInitialAad, 0, mInitialAad.Length);
+ }
+ }
+ }
+}
diff --git a/crypto/src/crypto/modes/IAeadBlockCipher.cs b/crypto/src/crypto/modes/IAeadBlockCipher.cs
index 52c4ff428..ebe5ef234 100644
--- a/crypto/src/crypto/modes/IAeadBlockCipher.cs
+++ b/crypto/src/crypto/modes/IAeadBlockCipher.cs
@@ -1,105 +1,15 @@
-using Org.BouncyCastle.Crypto.Parameters;
+using System;
namespace Org.BouncyCastle.Crypto.Modes
{
- /// <summary>
- /// A block cipher mode that includes authenticated encryption with a streaming mode
- /// and optional associated data.</summary>
- /// <see cref="AeadParameters"/>
+ /// <summary>An IAeadCipher based on an IBlockCipher.</summary>
public interface IAeadBlockCipher
+ : IAeadCipher
{
- /// <summary>The name of the algorithm this cipher implements.</summary>
- string AlgorithmName { get; }
+ /// <returns>The block size for this cipher, in bytes.</returns>
+ int GetBlockSize();
- /// <summary>The block cipher underlying this algorithm.</summary>
+ /// <summary>The block cipher underlying this algorithm.</summary>
IBlockCipher GetUnderlyingCipher();
-
- /// <summary>Initialise the cipher.</summary>
- /// <remarks>Parameter can either be an AeadParameters or a ParametersWithIV object.</remarks>
- /// <param name="forEncryption">Initialise for encryption if true, for decryption if false.</param>
- /// <param name="parameters">The key or other data required by the cipher.</param>
- void Init(bool forEncryption, ICipherParameters parameters);
-
- /// <returns>The block size for this cipher, in bytes.</returns>
- int GetBlockSize();
-
- /// <summary>Add a single byte to the associated data check.</summary>
- /// <remarks>If the implementation supports it, this will be an online operation and will not retain the associated data.</remarks>
- /// <param name="input">The byte to be processed.</param>
- void ProcessAadByte(byte input);
-
- /// <summary>Add a sequence of bytes to the associated data check.</summary>
- /// <remarks>If the implementation supports it, this will be an online operation and will not retain the associated data.</remarks>
- /// <param name="inBytes">The input byte array.</param>
- /// <param name="inOff">The offset into the input array where the data to be processed starts.</param>
- /// <param name="len">The number of bytes to be processed.</param>
- void ProcessAadBytes(byte[] inBytes, int inOff, int len);
-
- /**
- * Encrypt/decrypt a single byte.
- *
- * @param input the byte to be processed.
- * @param outBytes the output buffer the processed byte goes into.
- * @param outOff the offset into the output byte array the processed data starts at.
- * @return the number of bytes written to out.
- * @exception DataLengthException if the output buffer is too small.
- */
- int ProcessByte(byte input, byte[] outBytes, int outOff);
-
- /**
- * Process a block of bytes from in putting the result into out.
- *
- * @param inBytes the input byte array.
- * @param inOff the offset into the in array where the data to be processed starts.
- * @param len the number of bytes to be processed.
- * @param outBytes the output buffer the processed bytes go into.
- * @param outOff the offset into the output byte array the processed data starts at.
- * @return the number of bytes written to out.
- * @exception DataLengthException if the output buffer is too small.
- */
- int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff);
-
- /**
- * Finish the operation either appending or verifying the MAC at the end of the data.
- *
- * @param outBytes space for any resulting output data.
- * @param outOff offset into out to start copying the data at.
- * @return number of bytes written into out.
- * @throws InvalidOperationException if the cipher is in an inappropriate state.
- * @throws InvalidCipherTextException if the MAC fails to match.
- */
- int DoFinal(byte[] outBytes, int outOff);
-
- /**
- * Return the value of the MAC associated with the last stream processed.
- *
- * @return MAC for plaintext data.
- */
- byte[] GetMac();
-
- /**
- * Return the size of the output buffer required for a ProcessBytes
- * an input of len bytes.
- *
- * @param len the length of the input.
- * @return the space required to accommodate a call to ProcessBytes
- * with len bytes of input.
- */
- int GetUpdateOutputSize(int len);
-
- /**
- * Return the size of the output buffer required for a ProcessBytes plus a
- * DoFinal with an input of len bytes.
- *
- * @param len the length of the input.
- * @return the space required to accommodate a call to ProcessBytes and DoFinal
- * with len bytes of input.
- */
- int GetOutputSize(int len);
-
- /// <summary>
- /// Reset the cipher to the same state as it was after the last init (if there was one).
- /// </summary>
- void Reset();
}
}
diff --git a/crypto/src/crypto/modes/IAeadCipher.cs b/crypto/src/crypto/modes/IAeadCipher.cs
new file mode 100644
index 000000000..437693cb6
--- /dev/null
+++ b/crypto/src/crypto/modes/IAeadCipher.cs
@@ -0,0 +1,111 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+ /// <summary>
+ /// A cipher mode that includes authenticated encryption with a streaming mode and optional
+ /// associated data.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface may operate in a packet mode (where all input data is
+ /// buffered and processed during the call to DoFinal, or in a streaming mode (where output
+ /// data is incrementally produced with each call to ProcessByte or ProcessBytes. This is
+ /// important to consider during decryption: in a streaming mode, unauthenticated plaintext
+ /// data may be output prior to the call to DoFinal that results in an authentication failure.
+ /// The higher level protocol utilising this cipher must ensure the plaintext data is handled
+ /// appropriately until the end of data is reached and the entire ciphertext is authenticated.
+ /// </remarks>
+ /// <see cref="AeadParameters"/>
+ public interface IAeadCipher
+ {
+ /// <summary>The name of the algorithm this cipher implements.</summary>
+ string AlgorithmName { get; }
+
+ /// <summary>Initialise the cipher.</summary>
+ /// <remarks>Parameter can either be an AeadParameters or a ParametersWithIV object.</remarks>
+ /// <param name="forEncryption">Initialise for encryption if true, for decryption if false.</param>
+ /// <param name="parameters">The key or other data required by the cipher.</param>
+ void Init(bool forEncryption, ICipherParameters parameters);
+
+ /// <summary>Add a single byte to the associated data check.</summary>
+ /// <remarks>If the implementation supports it, this will be an online operation and will not retain the associated data.</remarks>
+ /// <param name="input">The byte to be processed.</param>
+ void ProcessAadByte(byte input);
+
+ /// <summary>Add a sequence of bytes to the associated data check.</summary>
+ /// <remarks>If the implementation supports it, this will be an online operation and will not retain the associated data.</remarks>
+ /// <param name="inBytes">The input byte array.</param>
+ /// <param name="inOff">The offset into the input array where the data to be processed starts.</param>
+ /// <param name="len">The number of bytes to be processed.</param>
+ void ProcessAadBytes(byte[] inBytes, int inOff, int len);
+
+ /**
+ * Encrypt/decrypt a single byte.
+ *
+ * @param input the byte to be processed.
+ * @param outBytes the output buffer the processed byte goes into.
+ * @param outOff the offset into the output byte array the processed data starts at.
+ * @return the number of bytes written to out.
+ * @exception DataLengthException if the output buffer is too small.
+ */
+ int ProcessByte(byte input, byte[] outBytes, int outOff);
+
+ /**
+ * Process a block of bytes from in putting the result into out.
+ *
+ * @param inBytes the input byte array.
+ * @param inOff the offset into the in array where the data to be processed starts.
+ * @param len the number of bytes to be processed.
+ * @param outBytes the output buffer the processed bytes go into.
+ * @param outOff the offset into the output byte array the processed data starts at.
+ * @return the number of bytes written to out.
+ * @exception DataLengthException if the output buffer is too small.
+ */
+ int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff);
+
+ /**
+ * Finish the operation either appending or verifying the MAC at the end of the data.
+ *
+ * @param outBytes space for any resulting output data.
+ * @param outOff offset into out to start copying the data at.
+ * @return number of bytes written into out.
+ * @throws InvalidOperationException if the cipher is in an inappropriate state.
+ * @throws InvalidCipherTextException if the MAC fails to match.
+ */
+ int DoFinal(byte[] outBytes, int outOff);
+
+ /**
+ * Return the value of the MAC associated with the last stream processed.
+ *
+ * @return MAC for plaintext data.
+ */
+ byte[] GetMac();
+
+ /**
+ * Return the size of the output buffer required for a ProcessBytes
+ * an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to ProcessBytes
+ * with len bytes of input.
+ */
+ int GetUpdateOutputSize(int len);
+
+ /**
+ * Return the size of the output buffer required for a ProcessBytes plus a
+ * DoFinal with an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to ProcessBytes and DoFinal
+ * with len bytes of input.
+ */
+ int GetOutputSize(int len);
+
+ /// <summary>
+ /// Reset the cipher to the same state as it was after the last init (if there was one).
+ /// </summary>
+ void Reset();
+ }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index 0ab02f02e..5b39de6ad 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -168,6 +168,7 @@
<Compile Include="src\crypto\test\BlowfishTest.cs" />
<Compile Include="src\crypto\test\CAST6Test.cs" />
<Compile Include="src\crypto\test\CCMTest.cs" />
+ <Compile Include="src\crypto\test\ChaCha20Poly1305Test.cs" />
<Compile Include="src\crypto\test\ChaChaTest.cs" />
<Compile Include="src\crypto\test\CMacTest.cs" />
<Compile Include="src\crypto\test\CTSTest.cs" />
diff --git a/crypto/test/src/crypto/test/ChaCha20Poly1305Test.cs b/crypto/test/src/crypto/test/ChaCha20Poly1305Test.cs
new file mode 100644
index 000000000..3f74669dc
--- /dev/null
+++ b/crypto/test/src/crypto/test/ChaCha20Poly1305Test.cs
@@ -0,0 +1,443 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+ [TestFixture]
+ public class ChaCha20Poly1305Test
+ : SimpleTest
+ {
+ private static readonly string[][] TestVectors = new string[][]
+ {
+ new string[]
+ {
+ "Test Case 1",
+ "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ "4c616469657320616e642047656e746c"
+ + "656d656e206f662074686520636c6173"
+ + "73206f66202739393a20496620492063"
+ + "6f756c64206f6666657220796f75206f"
+ + "6e6c79206f6e652074697020666f7220"
+ + "746865206675747572652c2073756e73"
+ + "637265656e20776f756c642062652069"
+ + "742e",
+ "50515253c0c1c2c3c4c5c6c7",
+ "070000004041424344454647",
+ "d31a8d34648e60db7b86afbc53ef7ec2"
+ + "a4aded51296e08fea9e2b5a736ee62d6"
+ + "3dbea45e8ca9671282fafb69da92728b"
+ + "1a71de0a9e060b2905d6a5b67ecd3b36"
+ + "92ddbd7f2d778b8c9803aee328091b58"
+ + "fab324e4fad675945585808b4831d7bc"
+ + "3ff4def08e4b7a9de576d26586cec64b"
+ + "6116",
+ "1ae10b594f09e26a7e902ecbd0600691",
+ },
+ };
+
+ public override string Name
+ {
+ get { return "ChaCha20Poly1305"; }
+ }
+
+ public override void PerformTest()
+ {
+ for (int i = 0; i < TestVectors.Length; ++i)
+ {
+ RunTestCase(TestVectors[i]);
+ }
+
+ OutputSizeTests();
+ RandomTests();
+ TestExceptions();
+ }
+
+ private void CheckTestCase(
+ ChaCha20Poly1305 encCipher,
+ ChaCha20Poly1305 decCipher,
+ string testName,
+ byte[] SA,
+ byte[] P,
+ byte[] C,
+ byte[] T)
+ {
+ byte[] enc = new byte[encCipher.GetOutputSize(P.Length)];
+ if (SA != null)
+ {
+ encCipher.ProcessAadBytes(SA, 0, SA.Length);
+ }
+ int len = encCipher.ProcessBytes(P, 0, P.Length, enc, 0);
+ len += encCipher.DoFinal(enc, len);
+
+ if (enc.Length != len)
+ {
+ Fail("encryption reported incorrect length: " + testName);
+ }
+
+ byte[] mac = encCipher.GetMac();
+
+ byte[] data = new byte[P.Length];
+ Array.Copy(enc, 0, data, 0, data.Length);
+ byte[] tail = new byte[enc.Length - P.Length];
+ Array.Copy(enc, P.Length, tail, 0, tail.Length);
+
+ if (!AreEqual(C, data))
+ {
+ Fail("incorrect encrypt in: " + testName);
+ }
+
+ if (!AreEqual(T, mac))
+ {
+ Fail("getMac() returned wrong mac in: " + testName);
+ }
+
+ if (!AreEqual(T, tail))
+ {
+ Fail("stream contained wrong mac in: " + testName);
+ }
+
+ byte[] dec = new byte[decCipher.GetOutputSize(enc.Length)];
+ if (SA != null)
+ {
+ decCipher.ProcessAadBytes(SA, 0, SA.Length);
+ }
+ len = decCipher.ProcessBytes(enc, 0, enc.Length, dec, 0);
+ len += decCipher.DoFinal(dec, len);
+ mac = decCipher.GetMac();
+
+ data = new byte[C.Length];
+ Array.Copy(dec, 0, data, 0, data.Length);
+
+ if (!AreEqual(P, data))
+ {
+ Fail("incorrect decrypt in: " + testName);
+ }
+ }
+
+ private ChaCha20Poly1305 InitCipher(bool forEncryption, AeadParameters parameters)
+ {
+ ChaCha20Poly1305 c = new ChaCha20Poly1305();
+ c.Init(forEncryption, parameters);
+ return c;
+ }
+
+ private static int NextInt(SecureRandom rand, int n)
+ {
+ if ((n & -n) == n) // i.e., n is a power of 2
+ {
+ return (int)(((uint)n * (ulong)((uint)rand.NextInt() >> 1)) >> 31);
+ }
+
+ int bits, value;
+ do
+ {
+ bits = (int)((uint)rand.NextInt() >> 1);
+ value = bits % n;
+ }
+ while (bits - value + (n - 1) < 0);
+
+ return value;
+ }
+
+ private void OutputSizeTests()
+ {
+ byte[] K = new byte[32];
+ byte[] A = null;
+ byte[] N = new byte[12];
+
+ AeadParameters parameters = new AeadParameters(new KeyParameter(K), 16 * 8, N, A);
+ ChaCha20Poly1305 cipher = InitCipher(true, parameters);
+
+ if (cipher.GetUpdateOutputSize(0) != 0)
+ {
+ Fail("incorrect getUpdateOutputSize for initial 0 bytes encryption");
+ }
+
+ if (cipher.GetOutputSize(0) != 16)
+ {
+ Fail("incorrect getOutputSize for initial 0 bytes encryption");
+ }
+
+ cipher.Init(false, parameters);
+
+ if (cipher.GetUpdateOutputSize(0) != 0)
+ {
+ Fail("incorrect getUpdateOutputSize for initial 0 bytes decryption");
+ }
+
+ // NOTE: 0 bytes would be truncated data, but we want it to fail in the doFinal, not here
+ if (cipher.GetOutputSize(0) != 0)
+ {
+ Fail("fragile getOutputSize for initial 0 bytes decryption");
+ }
+
+ if (cipher.GetOutputSize(16) != 0)
+ {
+ Fail("incorrect getOutputSize for initial MAC-size bytes decryption");
+ }
+ }
+
+ private void RandomTests()
+ {
+ SecureRandom random = new SecureRandom();
+ random.SetSeed(DateTimeUtilities.CurrentUnixMs());
+
+ for (int i = 0; i < 10; ++i)
+ {
+ RandomTest(random);
+ }
+ }
+
+ private void RandomTest(SecureRandom random)
+ {
+ int kLength = 32;
+ byte[] K = new byte[kLength];
+ random.NextBytes(K);
+
+ int pLength = random.Next(65536);
+ byte[] P = new byte[pLength];
+ random.NextBytes(P);
+
+ int aLength = random.Next(256);
+ byte[] A = new byte[aLength];
+ random.NextBytes(A);
+
+ int saLength = random.Next(256);
+ byte[] SA = new byte[saLength];
+ random.NextBytes(SA);
+
+ int nonceLength = 12;
+ byte[] nonce = new byte[nonceLength];
+ random.NextBytes(nonce);
+
+ AeadParameters parameters = new AeadParameters(new KeyParameter(K), 16 * 8, nonce, A);
+ ChaCha20Poly1305 cipher = InitCipher(true, parameters);
+ byte[] C = new byte[cipher.GetOutputSize(P.Length)];
+ int predicted = cipher.GetUpdateOutputSize(P.Length);
+
+ int split = NextInt(random, SA.Length + 1);
+ cipher.ProcessAadBytes(SA, 0, split);
+ cipher.ProcessAadBytes(SA, split, SA.Length - split);
+
+ int len = cipher.ProcessBytes(P, 0, P.Length, C, 0);
+ if (predicted != len)
+ {
+ Fail("encryption reported incorrect update length in randomised test");
+ }
+
+ len += cipher.DoFinal(C, len);
+ if (C.Length != len)
+ {
+ Fail("encryption reported incorrect length in randomised test");
+ }
+
+ byte[] encT = cipher.GetMac();
+ byte[] tail = new byte[C.Length - P.Length];
+ Array.Copy(C, P.Length, tail, 0, tail.Length);
+
+ if (!AreEqual(encT, tail))
+ {
+ Fail("stream contained wrong mac in randomised test");
+ }
+
+ cipher.Init(false, parameters);
+ byte[] decP = new byte[cipher.GetOutputSize(C.Length)];
+ predicted = cipher.GetUpdateOutputSize(C.Length);
+
+ split = NextInt(random, SA.Length + 1);
+ cipher.ProcessAadBytes(SA, 0, split);
+ cipher.ProcessAadBytes(SA, split, SA.Length - split);
+
+ len = cipher.ProcessBytes(C, 0, C.Length, decP, 0);
+ if (predicted != len)
+ {
+ Fail("decryption reported incorrect update length in randomised test");
+ }
+
+ len += cipher.DoFinal(decP, len);
+
+ if (!AreEqual(P, decP))
+ {
+ Fail("incorrect decrypt in randomised test");
+ }
+
+ byte[] decT = cipher.GetMac();
+ if (!AreEqual(encT, decT))
+ {
+ Fail("decryption produced different mac from encryption");
+ }
+
+ //
+ // key reuse test
+ //
+ cipher.Init(false, AeadTestUtilities.ReuseKey(parameters));
+ decP = new byte[cipher.GetOutputSize(C.Length)];
+
+ split = NextInt(random, SA.Length + 1);
+ cipher.ProcessAadBytes(SA, 0, split);
+ cipher.ProcessAadBytes(SA, split, SA.Length - split);
+
+ len = cipher.ProcessBytes(C, 0, C.Length, decP, 0);
+ len += cipher.DoFinal(decP, len);
+
+ if (!AreEqual(P, decP))
+ {
+ Fail("incorrect decrypt in randomised test");
+ }
+
+ decT = cipher.GetMac();
+ if (!AreEqual(encT, decT))
+ {
+ Fail("decryption produced different mac from encryption");
+ }
+ }
+
+ private void RunTestCase(string[] testVector)
+ {
+ int pos = 0;
+ string testName = testVector[pos++];
+ byte[] K = Hex.DecodeStrict(testVector[pos++]);
+ byte[] P = Hex.DecodeStrict(testVector[pos++]);
+ byte[] A = Hex.DecodeStrict(testVector[pos++]);
+ byte[] N = Hex.DecodeStrict(testVector[pos++]);
+ byte[] C = Hex.DecodeStrict(testVector[pos++]);
+ byte[] T = Hex.DecodeStrict(testVector[pos++]);
+
+ RunTestCase(testName, K, N, A, P, C, T);
+ }
+
+ private void RunTestCase(
+ string testName,
+ byte[] K,
+ byte[] N,
+ byte[] A,
+ byte[] P,
+ byte[] C,
+ byte[] T)
+ {
+ byte[] fa = new byte[A.Length / 2];
+ byte[] la = new byte[A.Length - (A.Length / 2)];
+ Array.Copy(A, 0, fa, 0, fa.Length);
+ Array.Copy(A, fa.Length, la, 0, la.Length);
+
+ RunTestCase(testName + " all initial associated data", K, N, A, null, P, C, T);
+ RunTestCase(testName + " all subsequent associated data", K, N, null, A, P, C, T);
+ RunTestCase(testName + " split associated data", K, N, fa, la, P, C, T);
+ }
+
+ private void RunTestCase(
+ string testName,
+ byte[] K,
+ byte[] N,
+ byte[] A,
+ byte[] SA,
+ byte[] P,
+ byte[] C,
+ byte[] T)
+ {
+ AeadParameters parameters = new AeadParameters(new KeyParameter(K), T.Length * 8, N, A);
+ ChaCha20Poly1305 encCipher = InitCipher(true, parameters);
+ ChaCha20Poly1305 decCipher = InitCipher(false, parameters);
+ CheckTestCase(encCipher, decCipher, testName, SA, P, C, T);
+ encCipher = InitCipher(true, parameters);
+ CheckTestCase(encCipher, decCipher, testName + " (reused)", SA, P, C, T);
+
+ // Key reuse
+ AeadParameters keyReuseParams = AeadTestUtilities.ReuseKey(parameters);
+
+ try
+ {
+ encCipher.Init(true, keyReuseParams);
+ Fail("no exception");
+ }
+ catch (ArgumentException e)
+ {
+ IsTrue("wrong message", "cannot reuse nonce for ChaCha20Poly1305 encryption".Equals(e.Message));
+ }
+ }
+
+ private void TestExceptions()
+ {
+ ChaCha20Poly1305 c = new ChaCha20Poly1305();
+
+ try
+ {
+ c = new ChaCha20Poly1305(new SipHash());
+
+ Fail("incorrect mac size not picked up");
+ }
+ catch (ArgumentException e)
+ {
+ // expected
+ }
+
+ try
+ {
+ c.Init(false, new KeyParameter(new byte[32]));
+
+ Fail("illegal argument not picked up");
+ }
+ catch (ArgumentException e)
+ {
+ // expected
+ }
+
+ AeadTestUtilities.TestTampering(this, c, new AeadParameters(new KeyParameter(new byte[32]), 128, new byte[12]));
+
+ byte[] P = Strings.ToByteArray("Hello world!");
+ byte[] buf = new byte[100];
+
+ c = new ChaCha20Poly1305();
+ AeadParameters aeadParameters = new AeadParameters(new KeyParameter(new byte[32]), 128, new byte[12]);
+ c.Init(true, aeadParameters);
+
+ c.ProcessBytes(P, 0, P.Length, buf, 0);
+
+ c.DoFinal(buf, 0);
+
+ try
+ {
+ c.DoFinal(buf, 0);
+ Fail("no exception on reuse");
+ }
+ catch (InvalidOperationException e)
+ {
+ IsTrue("wrong message", e.Message.Equals("ChaCha20Poly1305 cannot be reused for encryption"));
+ }
+
+ try
+ {
+ c.Init(true, aeadParameters);
+ Fail("no exception on reuse");
+ }
+ catch (ArgumentException e)
+ {
+ IsTrue("wrong message", e.Message.Equals("cannot reuse nonce for ChaCha20Poly1305 encryption"));
+ }
+ }
+
+ public static void Main(string[] args)
+ {
+ RunTest(new ChaCha20Poly1305Test());
+ }
+
+ [Test]
+ public void TestFunction()
+ {
+ string resultText = Perform().ToString();
+
+ Assert.AreEqual(Name + ": Okay", resultText);
+ }
+ }
+}
diff --git a/crypto/test/src/crypto/test/RegressionTest.cs b/crypto/test/src/crypto/test/RegressionTest.cs
index 2bc55e8a2..aa9cd0483 100644
--- a/crypto/test/src/crypto/test/RegressionTest.cs
+++ b/crypto/test/src/crypto/test/RegressionTest.cs
@@ -105,6 +105,7 @@ namespace Org.BouncyCastle.Crypto.Tests
new Salsa20Test(),
new XSalsa20Test(),
new ChaChaTest(),
+ new ChaCha20Poly1305Test(),
new CMacTest(),
new EaxTest(),
new GcmTest(),
|