summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2019-09-09 21:01:14 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2019-09-09 21:01:14 +0700
commitb4b389a9e08375c78a25b63aca2468abe75e5428 (patch)
treeed2f1d614f0109c375f5b6dccfc6c005080af994
parentTampering and reuse test cases for GCM (diff)
downloadBouncyCastle.NET-ed25519-b4b389a9e08375c78a25b63aca2468abe75e5428.tar.xz
Port ChaCha20Poly1305 from bc-java
-rw-r--r--crypto/BouncyCastle.Android.csproj2
-rw-r--r--crypto/BouncyCastle.csproj2
-rw-r--r--crypto/BouncyCastle.iOS.csproj2
-rw-r--r--crypto/crypto.csproj15
-rw-r--r--crypto/src/crypto/modes/ChaCha20Poly1305.cs551
-rw-r--r--crypto/src/crypto/modes/IAeadBlockCipher.cs102
-rw-r--r--crypto/src/crypto/modes/IAeadCipher.cs111
-rw-r--r--crypto/test/UnitTests.csproj1
-rw-r--r--crypto/test/src/crypto/test/ChaCha20Poly1305Test.cs443
-rw-r--r--crypto/test/src/crypto/test/RegressionTest.cs1
10 files changed, 1134 insertions, 96 deletions
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(),