summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2014-08-21 16:24:10 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2014-08-21 16:24:10 +0700
commit5577cd5da173e2ba65b2f88980a2d7383a822750 (patch)
tree9f93476870062a143b2f6effc6acce5305228a96
parentA few minor followups to the previous batch of TLS updates (diff)
downloadBouncyCastle.NET-ed25519-5577cd5da173e2ba65b2f88980a2d7383a822750.tar.xz
More TLS ported from Java API
-rw-r--r--crypto/crypto.csproj20
-rw-r--r--crypto/src/crypto/engines/IesEngine.cs2
-rw-r--r--crypto/src/crypto/macs/Poly1305.cs543
-rw-r--r--crypto/src/crypto/tls/AbstractTlsCipherFactory.cs15
-rw-r--r--crypto/src/crypto/tls/Chacha20Poly1305.cs153
-rw-r--r--crypto/src/crypto/tls/DefaultTlsCipherFactory.cs204
-rw-r--r--crypto/src/crypto/tls/DefaultTlsClient.cs11
-rw-r--r--crypto/src/crypto/tls/PskTlsClient.cs8
-rw-r--r--crypto/src/crypto/tls/RecordStream.cs20
-rw-r--r--crypto/src/crypto/tls/SecurityParameters.cs46
-rw-r--r--crypto/src/crypto/tls/SrpTlsClient.cs9
-rw-r--r--crypto/src/crypto/tls/Ssl3Mac.cs206
-rw-r--r--crypto/src/crypto/tls/TlsAeadCipher.cs189
-rw-r--r--crypto/src/crypto/tls/TlsBlockCipher.cs337
-rw-r--r--crypto/src/crypto/tls/TlsCipher.cs6
-rw-r--r--crypto/src/crypto/tls/TlsCipherFactory.cs3
-rw-r--r--crypto/src/crypto/tls/TlsHandshakeHash.cs22
-rw-r--r--crypto/src/crypto/tls/TlsMac.cs201
-rw-r--r--crypto/src/crypto/tls/TlsNullCipher.cs108
-rw-r--r--crypto/src/crypto/tls/TlsProtocolHandler.cs52
-rw-r--r--crypto/src/crypto/tls/TlsStreamCipher.cs185
-rw-r--r--crypto/src/crypto/tls/TlsUtilities.cs224
-rw-r--r--crypto/src/util/Arrays.cs89
-rw-r--r--crypto/test/src/crypto/test/OCBTest.cs2
24 files changed, 1944 insertions, 711 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 7e0a21bda..fc577c303 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4274,6 +4274,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\AbstractTlsCipherFactory.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\AbstractTlsContext.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4359,6 +4364,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\Chacha20Poly1305.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\ChangeCipherSpec.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4604,6 +4614,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsAeadCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsAgreementCredentials.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4719,6 +4734,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsHandshakeHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsKeyExchange.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/engines/IesEngine.cs b/crypto/src/crypto/engines/IesEngine.cs
index 6520c86f8..70df3077c 100644
--- a/crypto/src/crypto/engines/IesEngine.cs
+++ b/crypto/src/crypto/engines/IesEngine.cs
@@ -133,7 +133,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             inOff += inLen;
 
-            byte[] T1 = Arrays.Copy(in_enc, inOff, macBuf.Length);
+            byte[] T1 = Arrays.CopyOfRange(in_enc, inOff, inOff + macBuf.Length);
 
             if (!Arrays.ConstantTimeAreEqual(T1, macBuf))
                 throw (new InvalidCipherTextException("Invalid MAC."));
diff --git a/crypto/src/crypto/macs/Poly1305.cs b/crypto/src/crypto/macs/Poly1305.cs
index 2d453b6ad..1a951ca04 100644
--- a/crypto/src/crypto/macs/Poly1305.cs
+++ b/crypto/src/crypto/macs/Poly1305.cs
@@ -7,266 +7,285 @@ using Org.BouncyCastle.Crypto.Utilities;
 namespace Org.BouncyCastle.Crypto.Macs
 {
 
-	/// <summary>
-	/// Poly1305 message authentication code, designed by D. J. Bernstein.
-	/// </summary>
-	/// <remarks>
-	/// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key
-	/// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106
-	/// effective key bits) used in the authenticator.
-	/// 
-	/// The polynomial calculation in this implementation is adapted from the public domain <a
-	/// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation
-	/// by Andrew M (@floodyberry).
-	/// </remarks>
-	/// <seealso cref="Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/>
-	public class Poly1305
-		: IMac
-	{
-		private const int BLOCK_SIZE = 16;
-
-		private readonly IBlockCipher cipher;
-
-		private readonly byte[] singleByte = new byte[1];
-
-		// Initialised state
-
-		/** Polynomial key */
-		private uint r0, r1, r2, r3, r4;
-
-		/** Precomputed 5 * r[1..4] */
-		private uint s1, s2, s3, s4;
-
-		/** Encrypted nonce */
-		private uint k0, k1, k2, k3;
-
-		// Accumulating state
-
-		/** Current block of buffered input */
-		private byte[] currentBlock = new byte[BLOCK_SIZE];
-
-		/** Current offset in input buffer */
-		private int currentBlockOffset = 0;
-
-		/** Polynomial accumulator */
-		private uint h0, h1, h2, h3, h4;
-
-		/**
-	     * Constructs a Poly1305 MAC, using a 128 bit block cipher.
-	     */
-		public Poly1305(IBlockCipher cipher)
-		{
-			if (cipher.GetBlockSize() != BLOCK_SIZE)
-			{
-				throw new ArgumentException("Poly1305 requires a 128 bit block cipher.");
-			}
-			this.cipher = cipher;
-		}
-
-		/// <summary>
-		/// Initialises the Poly1305 MAC.
-		/// </summary>
-		/// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with
-		///          a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param>
-		public void Init(ICipherParameters parameters)
-		{
-			byte[] nonce;
-			byte[] key;
-			if ((parameters is ParametersWithIV) && ((ParametersWithIV)parameters).Parameters is KeyParameter)
-			{
-				nonce = ((ParametersWithIV)parameters).GetIV();
-				key = ((KeyParameter)((ParametersWithIV)parameters).Parameters).GetKey();
-			}
-			else
-			{
-				throw new ArgumentException("Poly1305 requires a key and and IV.");
-			}
-
-			setKey(key, nonce);
-			Reset();
-		}
-
-		private void setKey(byte[] key, byte[] nonce)
-		{
-			if (nonce.Length != BLOCK_SIZE)
-			{
-				throw new ArgumentException("Poly1305 requires a 128 bit IV.");
-			}
-			Poly1305KeyGenerator.CheckKey(key);
-
-			// Extract r portion of key
-			uint t0 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 0);
-			uint t1 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 4);
-			uint t2 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 8);
-			uint t3 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 12);
-
-			r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6;
-			r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12;
-			r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18;
-			r3 = t2 & 0x3f03fff; t3 >>= 8;
-			r4 = t3 & 0x00fffff;
-
-			// Precompute multipliers
-			s1 = r1 * 5;
-			s2 = r2 * 5;
-			s3 = r3 * 5;
-			s4 = r4 * 5;
-
-			// Compute encrypted nonce
-			byte[] cipherKey = new byte[BLOCK_SIZE];
-			Array.Copy(key, 0, cipherKey, 0, cipherKey.Length);
-
-			cipher.Init(true, new KeyParameter(cipherKey));
-			cipher.ProcessBlock(nonce, 0, cipherKey, 0);
-
-			k0 = Pack.LE_To_UInt32(cipherKey, 0);
-			k1 = Pack.LE_To_UInt32(cipherKey, 4);
-			k2 = Pack.LE_To_UInt32(cipherKey, 8);
-			k3 = Pack.LE_To_UInt32(cipherKey, 12);
-		}
-
-		public string AlgorithmName
-		{
-			get { return "Poly1305-" + cipher.AlgorithmName; }
-		}
-
-		public int GetMacSize()
-		{
-			return BLOCK_SIZE;
-		}
-
-		public void Update(byte input)
-		{
-			singleByte[0] = input;
-			BlockUpdate(singleByte, 0, 1);
-		}
-
-		public void BlockUpdate(byte[] input, int inOff, int len)
-		{
-			int copied = 0;
-			while (len > copied)
-			{
-				if (currentBlockOffset == BLOCK_SIZE)
-				{
-					processBlock();
-					currentBlockOffset = 0;
-				}
-
-				int toCopy = System.Math.Min((len - copied), BLOCK_SIZE - currentBlockOffset);
-				Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy);
-				copied += toCopy;
-				currentBlockOffset += toCopy;
-			}
-
-		}
-
-		private void processBlock()
-		{
-			if (currentBlockOffset < BLOCK_SIZE)
-			{
-				currentBlock[currentBlockOffset] = 1;
-				for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++)
-				{
-					currentBlock[i] = 0;
-				}
-			}
-
-			ulong t0 = Pack.LE_To_UInt32(currentBlock, 0);
-			ulong t1 = Pack.LE_To_UInt32(currentBlock, 4);
-			ulong t2 = Pack.LE_To_UInt32(currentBlock, 8);
-			ulong t3 = Pack.LE_To_UInt32(currentBlock, 12);
-
-			h0 += (uint)(t0 & 0x3ffffffU);
-			h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff);
-			h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff);
-			h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff);
-			h4 += (uint)(t3 >> 8);
-
-			if (currentBlockOffset == BLOCK_SIZE)
-			{
-				h4 += (1 << 24);
-			}
-
-			ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1);
-			ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2);
-			ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3);
-			ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4);
-			ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0);
-
-			ulong b;
-			h0 = (uint)tp0 & 0x3ffffff; b = (tp0 >> 26);
-			tp1 += b; h1 = (uint)tp1 & 0x3ffffff; b = (tp1 >> 26);
-			tp2 += b; h2 = (uint)tp2 & 0x3ffffff; b = (tp2 >> 26);
-			tp3 += b; h3 = (uint)tp3 & 0x3ffffff; b = (tp3 >> 26);
-			tp4 += b; h4 = (uint)tp4 & 0x3ffffff; b = (tp4 >> 26);
-			h0 += (uint)(b * 5);
-		}
-
-		public int DoFinal(byte[] output, int outOff)
-		{
-			if (outOff + BLOCK_SIZE > output.Length)
-			{
-				throw new DataLengthException("Output buffer is too short.");
-			}
-
-			if (currentBlockOffset > 0)
-			{
-				// Process padded block
-				processBlock();
-			}
-
-			ulong f0, f1, f2, f3;
-
-			uint b = h0 >> 26;
-			h0 = h0 & 0x3ffffff;
-			h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff;
-			h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff;
-			h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff;
-			h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff;
-			h0 += b * 5;
-
-			uint g0, g1, g2, g3, g4;
-			g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff;
-			g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff;
-			g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff;
-			g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff;
-			g4 = h4 + b - (1 << 26);
-
-			b = (g4 >> 31) - 1;
-			uint nb = ~b;
-			h0 = (h0 & nb) | (g0 & b);
-			h1 = (h1 & nb) | (g1 & b);
-			h2 = (h2 & nb) | (g2 & b);
-			h3 = (h3 & nb) | (g3 & b);
-			h4 = (h4 & nb) | (g4 & b);
-
-			f0 = ((h0      ) | (h1 << 26)) + (ulong)k0;
-			f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1;
-			f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2;
-			f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3;
-
-			Pack.UInt32_To_LE((uint)f0, output, outOff);
-			f1 += (f0 >> 32);
-			Pack.UInt32_To_LE((uint)f1, output, outOff + 4);
-			f2 += (f1 >> 32);
-			Pack.UInt32_To_LE((uint)f2, output, outOff + 8);
-			f3 += (f2 >> 32);
-			Pack.UInt32_To_LE((uint)f3, output, outOff + 12);
-
-			Reset();
-			return BLOCK_SIZE;
-		}
-
-		public void Reset()
-		{
-			currentBlockOffset = 0;
-
-			h0 = h1 = h2 = h3 = h4 = 0;
-		}
-
-		private static ulong mul32x32_64(uint i1, uint i2)
-		{
-			return ((ulong)i1) * i2;
-		}
-	}
+    /// <summary>
+    /// Poly1305 message authentication code, designed by D. J. Bernstein.
+    /// </summary>
+    /// <remarks>
+    /// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key
+    /// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106
+    /// effective key bits) used in the authenticator.
+    /// 
+    /// The polynomial calculation in this implementation is adapted from the public domain <a
+    /// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation
+    /// by Andrew M (@floodyberry).
+    /// </remarks>
+    /// <seealso cref="Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/>
+    public class Poly1305
+        : IMac
+    {
+        private const int BLOCK_SIZE = 16;
+
+        private readonly IBlockCipher cipher;
+
+        private readonly byte[] singleByte = new byte[1];
+
+        // Initialised state
+
+        /** Polynomial key */
+        private uint r0, r1, r2, r3, r4;
+
+        /** Precomputed 5 * r[1..4] */
+        private uint s1, s2, s3, s4;
+
+        /** Encrypted nonce */
+        private uint k0, k1, k2, k3;
+
+        // Accumulating state
+
+        /** Current block of buffered input */
+        private byte[] currentBlock = new byte[BLOCK_SIZE];
+
+        /** Current offset in input buffer */
+        private int currentBlockOffset = 0;
+
+        /** Polynomial accumulator */
+        private uint h0, h1, h2, h3, h4;
+
+        /**
+         * Constructs a Poly1305 MAC, where the key passed to init() will be used directly.
+         */
+        public Poly1305()
+        {
+            this.cipher = null;
+        }
+
+        /**
+         * Constructs a Poly1305 MAC, using a 128 bit block cipher.
+         */
+        public Poly1305(IBlockCipher cipher)
+        {
+            if (cipher.GetBlockSize() != BLOCK_SIZE)
+            {
+                throw new ArgumentException("Poly1305 requires a 128 bit block cipher.");
+            }
+            this.cipher = cipher;
+        }
+
+        /// <summary>
+        /// Initialises the Poly1305 MAC.
+        /// </summary>
+        /// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with
+        ///          a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param>
+        public void Init(ICipherParameters parameters)
+        {
+            byte[] nonce = null;
+
+            if (cipher != null)
+            {
+                if (!(parameters is ParametersWithIV))
+                    throw new ArgumentException("Poly1305 requires an IV when used with a block cipher.", "parameters");
+
+                ParametersWithIV ivParams = (ParametersWithIV)parameters;
+                nonce = ivParams.GetIV();
+                parameters = ivParams.Parameters;
+            }
+
+            if (!(parameters is KeyParameter))
+                throw new ArgumentException("Poly1305 requires a key.");
+
+            KeyParameter keyParams = (KeyParameter)parameters;
+
+            SetKey(keyParams.GetKey(), nonce);
+
+            Reset();
+        }
+
+        private void SetKey(byte[] key, byte[] nonce)
+        {
+            if (cipher != null && (nonce == null || nonce.Length != BLOCK_SIZE))
+                throw new ArgumentException("Poly1305 requires a 128 bit IV.");
+
+            Poly1305KeyGenerator.CheckKey(key);
+
+            // Extract r portion of key
+            uint t0 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 0);
+            uint t1 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 4);
+            uint t2 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 8);
+            uint t3 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 12);
+
+            r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6;
+            r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12;
+            r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18;
+            r3 = t2 & 0x3f03fff; t3 >>= 8;
+            r4 = t3 & 0x00fffff;
+
+            // Precompute multipliers
+            s1 = r1 * 5;
+            s2 = r2 * 5;
+            s3 = r3 * 5;
+            s4 = r4 * 5;
+
+            byte[] kBytes;
+            if (cipher == null)
+            {
+                kBytes = key;
+            }
+            else
+            {
+                // Compute encrypted nonce
+                kBytes = new byte[BLOCK_SIZE];
+                cipher.Init(true, new KeyParameter(key, 0, BLOCK_SIZE));
+                cipher.ProcessBlock(nonce, 0, kBytes, 0);
+            }
+
+            k0 = Pack.LE_To_UInt32(kBytes, 0);
+            k1 = Pack.LE_To_UInt32(kBytes, 4);
+            k2 = Pack.LE_To_UInt32(kBytes, 8);
+            k3 = Pack.LE_To_UInt32(kBytes, 12);
+        }
+
+        public string AlgorithmName
+        {
+            get { return cipher == null ? "Poly1305" : "Poly1305-" + cipher.AlgorithmName; }
+        }
+
+        public int GetMacSize()
+        {
+            return BLOCK_SIZE;
+        }
+
+        public void Update(byte input)
+        {
+            singleByte[0] = input;
+            BlockUpdate(singleByte, 0, 1);
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int len)
+        {
+            int copied = 0;
+            while (len > copied)
+            {
+                if (currentBlockOffset == BLOCK_SIZE)
+                {
+                    processBlock();
+                    currentBlockOffset = 0;
+                }
+
+                int toCopy = System.Math.Min((len - copied), BLOCK_SIZE - currentBlockOffset);
+                Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy);
+                copied += toCopy;
+                currentBlockOffset += toCopy;
+            }
+
+        }
+
+        private void processBlock()
+        {
+            if (currentBlockOffset < BLOCK_SIZE)
+            {
+                currentBlock[currentBlockOffset] = 1;
+                for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++)
+                {
+                    currentBlock[i] = 0;
+                }
+            }
+
+            ulong t0 = Pack.LE_To_UInt32(currentBlock, 0);
+            ulong t1 = Pack.LE_To_UInt32(currentBlock, 4);
+            ulong t2 = Pack.LE_To_UInt32(currentBlock, 8);
+            ulong t3 = Pack.LE_To_UInt32(currentBlock, 12);
+
+            h0 += (uint)(t0 & 0x3ffffffU);
+            h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff);
+            h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff);
+            h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff);
+            h4 += (uint)(t3 >> 8);
+
+            if (currentBlockOffset == BLOCK_SIZE)
+            {
+                h4 += (1 << 24);
+            }
+
+            ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1);
+            ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2);
+            ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3);
+            ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4);
+            ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0);
+
+            ulong b;
+            h0 = (uint)tp0 & 0x3ffffff; b = (tp0 >> 26);
+            tp1 += b; h1 = (uint)tp1 & 0x3ffffff; b = (tp1 >> 26);
+            tp2 += b; h2 = (uint)tp2 & 0x3ffffff; b = (tp2 >> 26);
+            tp3 += b; h3 = (uint)tp3 & 0x3ffffff; b = (tp3 >> 26);
+            tp4 += b; h4 = (uint)tp4 & 0x3ffffff; b = (tp4 >> 26);
+            h0 += (uint)(b * 5);
+        }
+
+        public int DoFinal(byte[] output, int outOff)
+        {
+            if (outOff + BLOCK_SIZE > output.Length)
+            {
+                throw new DataLengthException("Output buffer is too short.");
+            }
+
+            if (currentBlockOffset > 0)
+            {
+                // Process padded block
+                processBlock();
+            }
+
+            ulong f0, f1, f2, f3;
+
+            uint b = h0 >> 26;
+            h0 = h0 & 0x3ffffff;
+            h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff;
+            h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff;
+            h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff;
+            h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff;
+            h0 += b * 5;
+
+            uint g0, g1, g2, g3, g4;
+            g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff;
+            g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff;
+            g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff;
+            g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff;
+            g4 = h4 + b - (1 << 26);
+
+            b = (g4 >> 31) - 1;
+            uint nb = ~b;
+            h0 = (h0 & nb) | (g0 & b);
+            h1 = (h1 & nb) | (g1 & b);
+            h2 = (h2 & nb) | (g2 & b);
+            h3 = (h3 & nb) | (g3 & b);
+            h4 = (h4 & nb) | (g4 & b);
+
+            f0 = ((h0      ) | (h1 << 26)) + (ulong)k0;
+            f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1;
+            f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2;
+            f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3;
+
+            Pack.UInt32_To_LE((uint)f0, output, outOff);
+            f1 += (f0 >> 32);
+            Pack.UInt32_To_LE((uint)f1, output, outOff + 4);
+            f2 += (f1 >> 32);
+            Pack.UInt32_To_LE((uint)f2, output, outOff + 8);
+            f3 += (f2 >> 32);
+            Pack.UInt32_To_LE((uint)f3, output, outOff + 12);
+
+            Reset();
+            return BLOCK_SIZE;
+        }
+
+        public void Reset()
+        {
+            currentBlockOffset = 0;
+
+            h0 = h1 = h2 = h3 = h4 = 0;
+        }
+
+        private static ulong mul32x32_64(uint i1, uint i2)
+        {
+            return ((ulong)i1) * i2;
+        }
+    }
 }
diff --git a/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs b/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs
new file mode 100644
index 000000000..141ee6507
--- /dev/null
+++ b/crypto/src/crypto/tls/AbstractTlsCipherFactory.cs
@@ -0,0 +1,15 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class AbstractTlsCipherFactory
+        :   TlsCipherFactory
+    {
+        /// <exception cref="IOException"></exception>
+        public virtual TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/Chacha20Poly1305.cs b/crypto/src/crypto/tls/Chacha20Poly1305.cs
new file mode 100644
index 000000000..e4e4c7ee2
--- /dev/null
+++ b/crypto/src/crypto/tls/Chacha20Poly1305.cs
@@ -0,0 +1,153 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class Chacha20Poly1305
+        :   TlsCipher
+    {
+        protected readonly TlsContext context;
+
+        protected readonly ChaChaEngine encryptCipher;
+        protected readonly ChaChaEngine decryptCipher;
+
+        /// <exception cref="IOException"></exception>
+        public Chacha20Poly1305(TlsContext context)
+        {
+            if (!TlsUtilities.IsTlsV12(context))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.context = context;
+
+            byte[] key_block = TlsUtilities.CalculateKeyBlock(context, 64);
+
+            KeyParameter client_write_key = new KeyParameter(key_block, 0, 32);
+            KeyParameter server_write_key = new KeyParameter(key_block, 32, 32);
+
+            this.encryptCipher = new ChaChaEngine(20);
+            this.decryptCipher = new ChaChaEngine(20);
+
+            KeyParameter encryptKey, decryptKey;
+            if (context.IsServer)
+            {
+                encryptKey = server_write_key;
+                decryptKey = client_write_key;
+            }
+            else
+            {
+                encryptKey = client_write_key;
+                decryptKey = server_write_key;
+            }
+
+            byte[] dummyNonce = new byte[8];
+
+            this.encryptCipher.Init(true, new ParametersWithIV(encryptKey, dummyNonce));
+            this.decryptCipher.Init(false, new ParametersWithIV(decryptKey, dummyNonce));
+        }
+
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            return ciphertextLimit - 16;
+        }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len)
+        {
+            int ciphertextLength = len + 16;
+
+            KeyParameter macKey = InitRecordMac(encryptCipher, true, seqNo);
+
+            byte[] output = new byte[ciphertextLength];
+            encryptCipher.ProcessBytes(plaintext, offset, len, output, 0);
+
+            byte[] additionalData = GetAdditionalData(seqNo, type, len);
+            byte[] mac = CalculateRecordMac(macKey, additionalData, output, 0, len);
+            Array.Copy(mac, 0, output, len, mac.Length);
+
+            return output;
+        }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len)
+        {
+            if (GetPlaintextLimit(len) < 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int plaintextLength = len - 16;
+
+            byte[] receivedMAC = Arrays.CopyOfRange(ciphertext, offset + plaintextLength, offset + len);
+
+            KeyParameter macKey = InitRecordMac(decryptCipher, false, seqNo);
+
+            byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength);
+            byte[] calculatedMAC = CalculateRecordMac(macKey, additionalData, ciphertext, offset, plaintextLength);
+
+            if (!Arrays.ConstantTimeAreEqual(calculatedMAC, receivedMAC))
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
+            byte[] output = new byte[plaintextLength];
+            decryptCipher.ProcessBytes(ciphertext, offset, plaintextLength, output, 0);
+
+            return output;
+        }
+
+        protected virtual KeyParameter InitRecordMac(ChaChaEngine cipher, bool forEncryption, long seqNo)
+        {
+            byte[] nonce = new byte[8];
+            TlsUtilities.WriteUint64(seqNo, nonce, 0);
+
+            cipher.Init(forEncryption, new ParametersWithIV(null, nonce));
+
+            byte[] firstBlock = new byte[64];
+            cipher.ProcessBytes(firstBlock, 0, firstBlock.Length, firstBlock, 0);
+
+            // NOTE: The BC implementation puts 'r' after 'k'
+            Array.Copy(firstBlock, 0, firstBlock, 32, 16);
+            KeyParameter macKey = new KeyParameter(firstBlock, 16, 32);
+            Poly1305KeyGenerator.Clamp(macKey.GetKey());
+            return macKey;
+        }
+
+        protected virtual byte[] CalculateRecordMac(KeyParameter macKey, byte[] additionalData, byte[] buf, int off, int len)
+        {
+            IMac mac = new Poly1305();
+            mac.Init(macKey);
+
+            UpdateRecordMac(mac, additionalData, 0, additionalData.Length);
+            UpdateRecordMac(mac, buf, off, len);
+            return MacUtilities.DoFinal(mac);
+        }
+
+        protected virtual void UpdateRecordMac(IMac mac, byte[] buf, int off, int len)
+        {
+            mac.BlockUpdate(buf, off, len);
+
+            byte[] longLen = Pack.UInt64_To_LE((ulong)len);
+            mac.BlockUpdate(longLen, 0, longLen.Length);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len)
+        {
+            /*
+             * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
+             * TLSCompressed.length
+             */
+            byte[] additional_data = new byte[13];
+            TlsUtilities.WriteUint64(seqNo, additional_data, 0);
+            TlsUtilities.WriteUint8(type, additional_data, 8);
+            TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9);
+            TlsUtilities.WriteUint16(len, additional_data, 11);
+
+            return additional_data;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs b/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs
index cc34b3028..7c4213c25 100644
--- a/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs
+++ b/crypto/src/crypto/tls/DefaultTlsCipherFactory.cs
@@ -1,53 +1,146 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Modes;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
     public class DefaultTlsCipherFactory
-        : TlsCipherFactory
+        :   AbstractTlsCipherFactory
     {
-        public virtual TlsCipher CreateCipher(TlsContext context,
-            int encryptionAlgorithm, DigestAlgorithm digestAlgorithm)
+        /// <exception cref="IOException"></exception>
+        public override TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
         {
             switch (encryptionAlgorithm)
             {
-                case EncryptionAlgorithm.cls_3DES_EDE_CBC:
-                    return CreateDesEdeCipher(context, 24, digestAlgorithm);
-                case EncryptionAlgorithm.AES_128_CBC:
-                    return CreateAesCipher(context, 16, digestAlgorithm);
-                case EncryptionAlgorithm.AES_256_CBC:
-                    return CreateAesCipher(context, 32, digestAlgorithm);
-                case EncryptionAlgorithm.RC4_128:
-                    return CreateRC4Cipher(context, 16, digestAlgorithm);
-                default:
-                    throw new TlsFatalAlert(AlertDescription.internal_error);
+            case EncryptionAlgorithm.cls_3DES_EDE_CBC:
+                return CreateDesEdeCipher(context, macAlgorithm);
+            case EncryptionAlgorithm.AEAD_CHACHA20_POLY1305:
+                // NOTE: Ignores macAlgorithm
+                return CreateChaCha20Poly1305(context);
+            case EncryptionAlgorithm.AES_128_CBC:
+                return CreateAESCipher(context, 16, macAlgorithm);
+            case EncryptionAlgorithm.AES_128_CCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(context, 16, 16);
+            case EncryptionAlgorithm.AES_128_CCM_8:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(context, 16, 8);
+            case EncryptionAlgorithm.AES_256_CCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(context, 32, 16);
+            case EncryptionAlgorithm.AES_256_CCM_8:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(context, 32, 8);
+            case EncryptionAlgorithm.AES_128_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Gcm(context, 16, 16);
+            case EncryptionAlgorithm.AES_256_CBC:
+                return CreateAESCipher(context, 32, macAlgorithm);
+            case EncryptionAlgorithm.AES_256_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Gcm(context, 32, 16);
+            case EncryptionAlgorithm.CAMELLIA_128_CBC:
+                return CreateCamelliaCipher(context, 16, macAlgorithm);
+            case EncryptionAlgorithm.CAMELLIA_128_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Camellia_Gcm(context, 16, 16);
+            case EncryptionAlgorithm.CAMELLIA_256_CBC:
+                return CreateCamelliaCipher(context, 32, macAlgorithm);
+            case EncryptionAlgorithm.CAMELLIA_256_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Camellia_Gcm(context, 32, 16);
+            case EncryptionAlgorithm.ESTREAM_SALSA20:
+                return CreateSalsa20Cipher(context, 12, 32, macAlgorithm);
+            case EncryptionAlgorithm.NULL:
+                return CreateNullCipher(context, macAlgorithm);
+            case EncryptionAlgorithm.RC4_128:
+                return CreateRC4Cipher(context, 16, macAlgorithm);
+            case EncryptionAlgorithm.SALSA20:
+                return CreateSalsa20Cipher(context, 20, 32, macAlgorithm);
+            case EncryptionAlgorithm.SEED_CBC:
+                return CreateSeedCipher(context, macAlgorithm);
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
             }
         }
 
         /// <exception cref="IOException"></exception>
-        protected virtual TlsCipher CreateRC4Cipher(TlsContext context, int cipherKeySize, DigestAlgorithm digestAlgorithm)
+        protected virtual TlsBlockCipher CreateAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm)
         {
-            return new TlsStreamCipher(context, CreateRC4StreamCipher(), CreateRC4StreamCipher(), CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize);
+            return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(),
+                CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize);
         }
 
         /// <exception cref="IOException"></exception>
-        protected virtual TlsCipher CreateAesCipher(TlsContext context, int cipherKeySize,
-            DigestAlgorithm digestAlgorithm)
+        protected virtual TlsBlockCipher CreateCamelliaCipher(TlsContext context, int cipherKeySize, int macAlgorithm)
         {
-            return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(),
-                CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize);
+            return new TlsBlockCipher(context, CreateCamelliaBlockCipher(),
+                CreateCamelliaBlockCipher(), CreateHMacDigest(macAlgorithm),
+                CreateHMacDigest(macAlgorithm), cipherKeySize);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsCipher CreateChaCha20Poly1305(TlsContext context)
+        {
+            return new Chacha20Poly1305(context);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsAeadCipher CreateCipher_Aes_Ccm(TlsContext context, int cipherKeySize, int macSize)
+        {
+            return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Ccm(),
+                CreateAeadBlockCipher_Aes_Ccm(), cipherKeySize, macSize);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsAeadCipher CreateCipher_Aes_Gcm(TlsContext context, int cipherKeySize, int macSize)
+        {
+            return new TlsAeadCipher(context, CreateAeadBlockCipher_Aes_Gcm(),
+                CreateAeadBlockCipher_Aes_Gcm(), cipherKeySize, macSize);
         }
 
         /// <exception cref="IOException"></exception>
-        protected virtual TlsCipher CreateDesEdeCipher(TlsContext context, int cipherKeySize,
-            DigestAlgorithm digestAlgorithm)
+        protected virtual TlsAeadCipher CreateCipher_Camellia_Gcm(TlsContext context, int cipherKeySize, int macSize)
+        {
+            return new TlsAeadCipher(context, CreateAeadBlockCipher_Camellia_Gcm(),
+                CreateAeadBlockCipher_Camellia_Gcm(), cipherKeySize, macSize);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsBlockCipher CreateDesEdeCipher(TlsContext context, int macAlgorithm)
         {
             return new TlsBlockCipher(context, CreateDesEdeBlockCipher(), CreateDesEdeBlockCipher(),
-                CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize);
+                CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 24);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsNullCipher CreateNullCipher(TlsContext context, int macAlgorithm)
+        {
+            return new TlsNullCipher(context, CreateHMacDigest(macAlgorithm),
+                CreateHMacDigest(macAlgorithm));
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsStreamCipher CreateRC4Cipher(TlsContext context, int cipherKeySize, int macAlgorithm)
+        {
+            return new TlsStreamCipher(context, CreateRC4StreamCipher(), CreateRC4StreamCipher(),
+                CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize, false);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsStreamCipher CreateSalsa20Cipher(TlsContext context, int rounds, int cipherKeySize, int macAlgorithm)
+        {
+            return new TlsStreamCipher(context, CreateSalsa20StreamCipher(rounds), CreateSalsa20StreamCipher(rounds),
+                CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), cipherKeySize, true);
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual TlsBlockCipher CreateSeedCipher(TlsContext context, int macAlgorithm)
+        {
+            return new TlsBlockCipher(context, CreateSeedBlockCipher(), CreateSeedBlockCipher(),
+                CreateHMacDigest(macAlgorithm), CreateHMacDigest(macAlgorithm), 16);
         }
 
         protected virtual IBlockCipher CreateAesEngine()
@@ -55,11 +148,38 @@ namespace Org.BouncyCastle.Crypto.Tls
             return new AesEngine();
         }
 
+        protected virtual IBlockCipher CreateCamelliaEngine()
+        {
+            return new CamelliaEngine();
+        }
+
         protected virtual IBlockCipher CreateAesBlockCipher()
         {
             return new CbcBlockCipher(CreateAesEngine());
         }
 
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ccm()
+        {
+            return new CcmBlockCipher(CreateAesEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Gcm()
+        {
+            // TODO Consider allowing custom configuration of multiplier
+            return new GcmBlockCipher(CreateAesEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Camellia_Gcm()
+        {
+            // TODO Consider allowing custom configuration of multiplier
+            return new GcmBlockCipher(CreateCamelliaEngine());
+        }
+
+        protected virtual IBlockCipher CreateCamelliaBlockCipher()
+        {
+            return new CbcBlockCipher(CreateCamelliaEngine());
+        }
+
         protected virtual IBlockCipher CreateDesEdeBlockCipher()
         {
             return new CbcBlockCipher(new DesEdeEngine());
@@ -70,21 +190,35 @@ namespace Org.BouncyCastle.Crypto.Tls
             return new RC4Engine();
         }
 
+        protected virtual IStreamCipher CreateSalsa20StreamCipher(int rounds)
+        {
+            return new Salsa20Engine(rounds);
+        }
+
+        protected virtual IBlockCipher CreateSeedBlockCipher()
+        {
+            return new CbcBlockCipher(new SeedEngine());
+        }
+
         /// <exception cref="IOException"></exception>
-        protected virtual IDigest CreateDigest(DigestAlgorithm digestAlgorithm)
+        protected virtual IDigest CreateHMacDigest(int macAlgorithm)
         {
-            switch (digestAlgorithm)
+            switch (macAlgorithm)
             {
-                case DigestAlgorithm.MD5:
-                    return new MD5Digest();
-                case DigestAlgorithm.SHA:
-                    return new Sha1Digest();
-                case DigestAlgorithm.SHA256:
-                    return new Sha256Digest();
-                case DigestAlgorithm.SHA384:
-                    return new Sha384Digest();
-                default:
-                    throw new TlsFatalAlert(AlertDescription.internal_error);
+            case MacAlgorithm.cls_null:
+                return null;
+            case MacAlgorithm.hmac_md5:
+                return TlsUtilities.CreateHash(HashAlgorithm.md5);
+            case MacAlgorithm.hmac_sha1:
+                return TlsUtilities.CreateHash(HashAlgorithm.sha1);
+            case MacAlgorithm.hmac_sha256:
+                return TlsUtilities.CreateHash(HashAlgorithm.sha256);
+            case MacAlgorithm.hmac_sha384:
+                return TlsUtilities.CreateHash(HashAlgorithm.sha384);
+            case MacAlgorithm.hmac_sha512:
+                return TlsUtilities.CreateHash(HashAlgorithm.sha512);
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
             }
         }
     }
diff --git a/crypto/src/crypto/tls/DefaultTlsClient.cs b/crypto/src/crypto/tls/DefaultTlsClient.cs
index d59fae164..924e6ee2e 100644
--- a/crypto/src/crypto/tls/DefaultTlsClient.cs
+++ b/crypto/src/crypto/tls/DefaultTlsClient.cs
@@ -213,14 +213,15 @@ namespace Org.BouncyCastle.Crypto.Tls
                 case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
                 case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
                 case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
@@ -231,7 +232,8 @@ namespace Org.BouncyCastle.Crypto.Tls
                 case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
@@ -242,7 +244,8 @@ namespace Org.BouncyCastle.Crypto.Tls
                 case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 default:
                     /*
diff --git a/crypto/src/crypto/tls/PskTlsClient.cs b/crypto/src/crypto/tls/PskTlsClient.cs
index e60688155..f6d83b5be 100644
--- a/crypto/src/crypto/tls/PskTlsClient.cs
+++ b/crypto/src/crypto/tls/PskTlsClient.cs
@@ -163,25 +163,25 @@ namespace Org.BouncyCastle.Crypto.Tls
                 case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
                 case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
                     return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC,
-                        DigestAlgorithm.SHA);
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
                     return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC,
-                        DigestAlgorithm.SHA);
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
                     return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC,
-                        DigestAlgorithm.SHA);
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
                 case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
                     return cipherFactory.CreateCipher(context, EncryptionAlgorithm.RC4_128,
-                        DigestAlgorithm.SHA);
+                        MacAlgorithm.hmac_sha1);
 
                 default:
                     /*
diff --git a/crypto/src/crypto/tls/RecordStream.cs b/crypto/src/crypto/tls/RecordStream.cs
index ce8882cbe..d05c6c1c2 100644
--- a/crypto/src/crypto/tls/RecordStream.cs
+++ b/crypto/src/crypto/tls/RecordStream.cs
@@ -14,8 +14,11 @@ namespace Org.BouncyCastle.Crypto.Tls
         private TlsCompression writeCompression = null;
         private TlsCipher readCipher = null;
         private TlsCipher writeCipher = null;
+        private long readSeqNo = 0, writeSeqNo = 0;
         private MemoryStream buffer = new MemoryStream();
 
+        private TlsContext context = null;
+
         internal RecordStream(
             TlsProtocolHandler	handler,
             Stream				inStr,
@@ -24,23 +27,30 @@ namespace Org.BouncyCastle.Crypto.Tls
             this.handler = handler;
             this.inStr = inStr;
             this.outStr = outStr;
-            this.hash = new CombinedHash();
             this.readCompression = new TlsNullCompression();
             this.writeCompression = this.readCompression;
-            this.readCipher = new TlsNullCipher();
+        }
+
+        internal void Init(TlsContext context)
+        {
+            this.context = context;
+            this.readCipher = new TlsNullCipher(context);
             this.writeCipher = this.readCipher;
+            this.hash = new CombinedHash();
         }
 
         internal void ClientCipherSpecDecided(TlsCompression tlsCompression, TlsCipher tlsCipher)
         {
             this.writeCompression = tlsCompression;
             this.writeCipher = tlsCipher;
+            this.writeSeqNo = 0;
         }
 
         internal void ServerClientSpecReceived()
         {
             this.readCompression = this.writeCompression;
             this.readCipher = this.writeCipher;
+            this.readSeqNo = 0;
         }
 
         public void ReadData()
@@ -59,7 +69,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             byte[] buf = new byte[len];
             TlsUtilities.ReadFully(buf, inStr);
-            byte[] decoded = readCipher.DecodeCiphertext(contentType, buf, 0, buf.Length);
+            byte[] decoded = readCipher.DecodeCiphertext(readSeqNo++, contentType, buf, 0, buf.Length);
 
             Stream cOut = readCompression.Decompress(buffer);
 
@@ -91,13 +101,13 @@ namespace Org.BouncyCastle.Crypto.Tls
             byte[] ciphertext;
             if (cOut == buffer)
             {
-                ciphertext = writeCipher.EncodePlaintext(type, message, offset, len);
+                ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, message, offset, len);
             }
             else
             {
                 cOut.Write(message, offset, len);
                 cOut.Flush();
-                ciphertext = writeCipher.EncodePlaintext(type, buffer.GetBuffer(), 0, (int)buffer.Position);
+                ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, buffer.GetBuffer(), 0, (int)buffer.Position);
                 buffer.SetLength(0);
             }
 
diff --git a/crypto/src/crypto/tls/SecurityParameters.cs b/crypto/src/crypto/tls/SecurityParameters.cs
index 0707f3d40..de8c6f13a 100644
--- a/crypto/src/crypto/tls/SecurityParameters.cs
+++ b/crypto/src/crypto/tls/SecurityParameters.cs
@@ -6,18 +6,27 @@ namespace Org.BouncyCastle.Crypto.Tls
 {
     public class SecurityParameters
     {
+        internal int entity = -1;
+        internal int cipherSuite = -1;
+        internal byte compressionAlgorithm = CompressionMethod.NULL;
         internal int prfAlgorithm = -1;
+        internal int verifyDataLength = -1;
         internal byte[] masterSecret = null;
         internal byte[] clientRandom = null;
         internal byte[] serverRandom = null;
 
+        // TODO Keep these internal, since it's maybe not the ideal place for them
+        internal short maxFragmentLength = -1;
+        internal bool truncatedHMac = false;
+        internal bool encryptThenMac = false;
+
         internal void CopySessionParametersFrom(SecurityParameters other)
         {
-            //this.entity = other.entity;
-            //this.cipherSuite = other.cipherSuite;
-            //this.compressionAlgorithm = other.compressionAlgorithm;
+            this.entity = other.entity;
+            this.cipherSuite = other.cipherSuite;
+            this.compressionAlgorithm = other.compressionAlgorithm;
             this.prfAlgorithm = other.prfAlgorithm;
-            //this.verifyDataLength = other.verifyDataLength;
+            this.verifyDataLength = other.verifyDataLength;
             this.masterSecret = Arrays.Clone(other.masterSecret);
         }
 
@@ -31,6 +40,30 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
+         * @return {@link ConnectionEnd}
+         */
+        public virtual int Entity
+        {
+            get { return entity; }
+        }
+
+        /**
+         * @return {@link CipherSuite}
+         */
+        public virtual int CipherSuite
+        {
+            get { return cipherSuite; }
+        }
+
+        /**
+         * @return {@link CompressionMethod}
+         */
+        public byte CompressionAlgorithm
+        {
+            get { return compressionAlgorithm; }
+        }
+
+        /**
          * @return {@link PRFAlgorithm}
          */
         public virtual int PrfAlgorithm
@@ -38,6 +71,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             get { return prfAlgorithm; }
         }
 
+        public virtual int VerifyDataLength
+        {
+            get { return verifyDataLength; }
+        }
+
         public virtual byte[] MasterSecret
         {
             get { return masterSecret; }
diff --git a/crypto/src/crypto/tls/SrpTlsClient.cs b/crypto/src/crypto/tls/SrpTlsClient.cs
index 3769fc85d..dfd7603b8 100644
--- a/crypto/src/crypto/tls/SrpTlsClient.cs
+++ b/crypto/src/crypto/tls/SrpTlsClient.cs
@@ -168,17 +168,20 @@ namespace Org.BouncyCastle.Crypto.Tls
                 case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
                 case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
-                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA);
+                    return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC,
+                        MacAlgorithm.hmac_sha1);
 
                 default:
                     /*
diff --git a/crypto/src/crypto/tls/Ssl3Mac.cs b/crypto/src/crypto/tls/Ssl3Mac.cs
index b2f3f309e..8bdb342dc 100644
--- a/crypto/src/crypto/tls/Ssl3Mac.cs
+++ b/crypto/src/crypto/tls/Ssl3Mac.cs
@@ -6,109 +6,105 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-	/**
-	 * HMAC implementation based on original internet draft for HMAC (RFC 2104)
-	 * 
-	 * The difference is that padding is concatentated versus XORed with the key
-	 * 
-	 * H(K + opad, H(K + ipad, text))
-	 */
-	public class Ssl3Mac
-		: IMac
-	{
-		private const byte IPAD = 0x36;
-		private const byte OPAD = 0x5C;
-
-		internal static readonly byte[] MD5_IPAD = GenPad(IPAD, 48);
-		internal static readonly byte[] MD5_OPAD = GenPad(OPAD, 48);
-		internal static readonly byte[] SHA1_IPAD = GenPad(IPAD, 40);
-		internal static readonly byte[] SHA1_OPAD = GenPad(OPAD, 40);
-
-		private IDigest digest;
-
-		private byte[] secret;
-		private byte[] ipad, opad;
-
-		/**
-		 * Base constructor for one of the standard digest algorithms that the byteLength of
-		 * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1.
-		 * 
-		 * @param digest the digest.
-		 */
-		public Ssl3Mac(IDigest digest)
-		{
-			this.digest = digest;
-
-	        if (digest.GetDigestSize() == 20)
-	        {
-	            this.ipad = SHA1_IPAD;
-	            this.opad = SHA1_OPAD;
-	        }
-	        else
-	        {
-	            this.ipad = MD5_IPAD;
-	            this.opad = MD5_OPAD;
-	        }
-		}
-
-		public virtual string AlgorithmName
-		{
-			get { return digest.AlgorithmName + "/SSL3MAC"; }
-		}
-
-		public virtual void Init(ICipherParameters parameters)
-		{
-			secret = Arrays.Clone(((KeyParameter)parameters).GetKey());
-
-			Reset();
-		}
-
-		public virtual int GetMacSize()
-		{
-			return digest.GetDigestSize();
-		}
-
-		public virtual void Update(byte input)
-		{
-			digest.Update(input);
-		}
-
-		public virtual void BlockUpdate(byte[] input, int inOff, int len)
-		{
-			digest.BlockUpdate(input, inOff, len);
-		}
-
-		public virtual int DoFinal(byte[] output, int outOff)
-		{
-			byte[] tmp = new byte[digest.GetDigestSize()];
-			digest.DoFinal(tmp, 0);
-
-			digest.BlockUpdate(secret, 0, secret.Length);
-			digest.BlockUpdate(opad, 0, opad.Length);
-			digest.BlockUpdate(tmp, 0, tmp.Length);
-
-			int len = digest.DoFinal(output, outOff);
-
-			Reset();
-
-			return len;
-		}
-
-		/**
-		 * Reset the mac generator.
-		 */
-		public virtual void Reset()
-		{
-			digest.Reset();
-			digest.BlockUpdate(secret, 0, secret.Length);
-			digest.BlockUpdate(ipad, 0, ipad.Length);
-		}
-
-		private static byte[] GenPad(byte b, int count)
-		{
-			byte[] padding = new byte[count];
-			Arrays.Fill(padding, b);
-			return padding;
-		}
-	}
+    /**
+     * HMAC implementation based on original internet draft for HMAC (RFC 2104)
+     * 
+     * The difference is that padding is concatentated versus XORed with the key
+     * 
+     * H(K + opad, H(K + ipad, text))
+     */
+    public class Ssl3Mac
+        : IMac
+    {
+        private const byte IPAD_BYTE = 0x36;
+        private const byte OPAD_BYTE = 0x5C;
+
+        internal static readonly byte[] IPAD = GenPad(IPAD_BYTE, 48);
+        internal static readonly byte[] OPAD = GenPad(OPAD_BYTE, 48);
+
+        private readonly IDigest digest;
+        private readonly int padLength;
+
+        private byte[] secret;
+
+        /**
+         * Base constructor for one of the standard digest algorithms that the byteLength of
+         * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1.
+         * 
+         * @param digest the digest.
+         */
+        public Ssl3Mac(IDigest digest)
+        {
+            this.digest = digest;
+
+            if (digest.GetDigestSize() == 20)
+            {
+                this.padLength = 40;
+            }
+            else
+            {
+                this.padLength = 48;
+            }
+        }
+
+        public virtual string AlgorithmName
+        {
+            get { return digest.AlgorithmName + "/SSL3MAC"; }
+        }
+
+        public virtual void Init(ICipherParameters parameters)
+        {
+            secret = Arrays.Clone(((KeyParameter)parameters).GetKey());
+
+            Reset();
+        }
+
+        public virtual int GetMacSize()
+        {
+            return digest.GetDigestSize();
+        }
+
+        public virtual void Update(byte input)
+        {
+            digest.Update(input);
+        }
+
+        public virtual void BlockUpdate(byte[] input, int inOff, int len)
+        {
+            digest.BlockUpdate(input, inOff, len);
+        }
+
+        public virtual int DoFinal(byte[] output, int outOff)
+        {
+            byte[] tmp = new byte[digest.GetDigestSize()];
+            digest.DoFinal(tmp, 0);
+
+            digest.BlockUpdate(secret, 0, secret.Length);
+            digest.BlockUpdate(OPAD, 0, padLength);
+            digest.BlockUpdate(tmp, 0, tmp.Length);
+
+            int len = digest.DoFinal(output, outOff);
+
+            Reset();
+
+            return len;
+        }
+
+        /**
+         * Reset the mac generator.
+         */
+        public virtual void Reset()
+        {
+            digest.Reset();
+            digest.BlockUpdate(secret, 0, secret.Length);
+            digest.BlockUpdate(IPAD, 0, padLength);
+        }
+
+        private static byte[] GenPad(byte b, int count)
+        {
+            byte[] padding = new byte[count];
+            Arrays.Fill(padding, b);
+            return padding;
+        }
+    }
 }
diff --git a/crypto/src/crypto/tls/TlsAeadCipher.cs b/crypto/src/crypto/tls/TlsAeadCipher.cs
new file mode 100644
index 000000000..e66f92317
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsAeadCipher.cs
@@ -0,0 +1,189 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class TlsAeadCipher
+        :   TlsCipher
+    {
+        protected readonly TlsContext context;
+        protected readonly int macSize;
+        protected readonly int nonce_explicit_length;
+
+        protected readonly IAeadBlockCipher encryptCipher;
+        protected readonly IAeadBlockCipher decryptCipher;
+
+        protected readonly byte[] encryptImplicitNonce, decryptImplicitNonce;
+
+        /// <exception cref="IOException"></exception>
+        public TlsAeadCipher(TlsContext context, IAeadBlockCipher clientWriteCipher, IAeadBlockCipher serverWriteCipher,
+            int cipherKeySize, int macSize)
+        {
+            if (!TlsUtilities.IsTlsV12(context))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.context = context;
+            this.macSize = macSize;
+
+            // NOTE: Valid for RFC 5288/6655 ciphers but may need review for other AEAD ciphers
+            this.nonce_explicit_length = 8;
+
+            // TODO SecurityParameters.fixed_iv_length
+            int fixed_iv_length = 4;
+
+            int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length);
+
+            byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size);
+
+            int offset = 0;
+
+            KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+            KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+            byte[] client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length);
+            offset += fixed_iv_length;
+            byte[] server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + fixed_iv_length);
+            offset += fixed_iv_length;
+
+            if (offset != key_block_size)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            KeyParameter encryptKey, decryptKey;
+            if (context.IsServer)
+            {
+                this.encryptCipher = serverWriteCipher;
+                this.decryptCipher = clientWriteCipher;
+                this.encryptImplicitNonce = server_write_IV;
+                this.decryptImplicitNonce = client_write_IV;
+                encryptKey = server_write_key;
+                decryptKey = client_write_key;
+            }
+            else
+            {
+                this.encryptCipher = clientWriteCipher;
+                this.decryptCipher = serverWriteCipher;
+                this.encryptImplicitNonce = client_write_IV;
+                this.decryptImplicitNonce = server_write_IV;
+                encryptKey = client_write_key;
+                decryptKey = server_write_key;
+            }
+
+            byte[] dummyNonce = new byte[fixed_iv_length + nonce_explicit_length];
+
+            this.encryptCipher.Init(true, new AeadParameters(encryptKey, 8 * macSize, dummyNonce));
+            this.decryptCipher.Init(false, new AeadParameters(decryptKey, 8 * macSize, dummyNonce));
+        }
+
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            // TODO We ought to be able to ask the decryptCipher (independently of it's current state!)
+            return ciphertextLimit - macSize - nonce_explicit_length;
+        }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len)
+        {
+            byte[] nonce = new byte[this.encryptImplicitNonce.Length + nonce_explicit_length];
+            Array.Copy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.Length);
+
+            /*
+             * RFC 5288/6655 The nonce_explicit MAY be the 64-bit sequence number.
+             * 
+             * (May need review for other AEAD ciphers).
+             */
+            TlsUtilities.WriteUint64(seqNo, nonce, encryptImplicitNonce.Length);
+
+            int plaintextOffset = offset;
+            int plaintextLength = len;
+            int ciphertextLength = encryptCipher.GetOutputSize(plaintextLength);
+
+            byte[] output = new byte[nonce_explicit_length + ciphertextLength];
+            Array.Copy(nonce, encryptImplicitNonce.Length, output, 0, nonce_explicit_length);
+            int outputPos = nonce_explicit_length;
+
+            byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength);
+            AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData);
+
+            try
+            {
+                encryptCipher.Init(true, parameters);
+                outputPos += encryptCipher.ProcessBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos);
+                outputPos += encryptCipher.DoFinal(output, outputPos);
+            }
+            catch (Exception)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            if (outputPos != output.Length)
+            {
+                // NOTE: Existing AEAD cipher implementations all give exact output lengths
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return output;
+        }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len)
+        {
+            if (GetPlaintextLimit(len) < 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            byte[] nonce = new byte[this.decryptImplicitNonce.Length + nonce_explicit_length];
+            Array.Copy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.Length);
+            Array.Copy(ciphertext, offset, nonce, decryptImplicitNonce.Length, nonce_explicit_length);
+
+            int ciphertextOffset = offset + nonce_explicit_length;
+            int ciphertextLength = len - nonce_explicit_length;
+            int plaintextLength = decryptCipher.GetOutputSize(ciphertextLength);
+
+            byte[] output = new byte[plaintextLength];
+            int outputPos = 0;
+
+            byte[] additionalData = GetAdditionalData(seqNo, type, plaintextLength);
+            AeadParameters parameters = new AeadParameters(null, 8 * macSize, nonce, additionalData);
+
+            try
+            {
+                decryptCipher.Init(false, parameters);
+                outputPos += decryptCipher.ProcessBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos);
+                outputPos += decryptCipher.DoFinal(output, outputPos);
+            }
+            catch (Exception)
+            {
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+            }
+
+            if (outputPos != output.Length)
+            {
+                // NOTE: Existing AEAD cipher implementations all give exact output lengths
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return output;
+        }
+
+        /// <exception cref="IOException"></exception>
+        protected virtual byte[] GetAdditionalData(long seqNo, byte type, int len)
+        {
+            /*
+             * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
+             * TLSCompressed.length
+             */
+
+            byte[] additional_data = new byte[13];
+            TlsUtilities.WriteUint64(seqNo, additional_data, 0);
+            TlsUtilities.WriteUint8(type, additional_data, 8);
+            TlsUtilities.WriteVersion(context.ServerVersion, additional_data, 9);
+            TlsUtilities.WriteUint16(len, additional_data, 11);
+
+            return additional_data;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsBlockCipher.cs b/crypto/src/crypto/tls/TlsBlockCipher.cs
index b2c69127c..82c0318b2 100644
--- a/crypto/src/crypto/tls/TlsBlockCipher.cs
+++ b/crypto/src/crypto/tls/TlsBlockCipher.cs
@@ -1,8 +1,6 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
@@ -10,169 +8,302 @@ using Org.BouncyCastle.Utilities;
 namespace Org.BouncyCastle.Crypto.Tls
 {
     /// <summary>
-    /// A generic TLS 1.0 block cipher. This can be used for AES or 3DES for example.
+    /// A generic TLS 1.0-1.2 / SSLv3 block cipher. This can be used for AES or 3DES for example.
     /// </summary>
     public class TlsBlockCipher
-        : TlsCipher
+        :   TlsCipher
     {
-        protected TlsContext context;
-        protected byte[] randomData;
+        protected readonly TlsContext context;
+        protected readonly byte[] randomData;
+        protected readonly bool useExplicitIV;
+        protected readonly bool encryptThenMac;
 
-        protected IBlockCipher encryptCipher;
-        protected IBlockCipher decryptCipher;
+        protected readonly IBlockCipher encryptCipher;
+        protected readonly IBlockCipher decryptCipher;
 
-        protected TlsMac wMac;
-        protected TlsMac rMac;
+        protected readonly TlsMac mWriteMac;
+        protected readonly TlsMac mReadMac;
 
         public virtual TlsMac WriteMac
         {
-            get { return wMac; }
+            get { return mWriteMac; }
         }
 
         public virtual TlsMac ReadMac
         {
-            get { return rMac; }
+            get { return mReadMac; }
         }
 
-        public TlsBlockCipher(TlsContext context, IBlockCipher encryptCipher,
-            IBlockCipher decryptCipher, IDigest writeDigest, IDigest readDigest, int cipherKeySize)
+        /// <exception cref="IOException"></exception>
+        public TlsBlockCipher(TlsContext context, IBlockCipher clientWriteCipher, IBlockCipher serverWriteCipher,
+            IDigest clientWriteDigest, IDigest serverWriteDigest, int cipherKeySize)
         {
             this.context = context;
 
             this.randomData = new byte[256];
-            context.SecureRandom.NextBytes(randomData);
+            context.NonceRandomGenerator.NextBytes(randomData);
 
-            this.encryptCipher = encryptCipher;
-            this.decryptCipher = decryptCipher;
+            this.useExplicitIV = TlsUtilities.IsTlsV11(context);
+            this.encryptThenMac = context.SecurityParameters.encryptThenMac;
 
-            int prfSize = (2 * cipherKeySize) + writeDigest.GetDigestSize()
-                + readDigest.GetDigestSize() + encryptCipher.GetBlockSize()
-                + decryptCipher.GetBlockSize();
+            int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize()
+                + serverWriteDigest.GetDigestSize();
 
-            SecurityParameters securityParameters = context.SecurityParameters;
+            // From TLS 1.1 onwards, block ciphers don't need client_write_IV
+            if (!useExplicitIV)
+            {
+                key_block_size += clientWriteCipher.GetBlockSize() + serverWriteCipher.GetBlockSize();
+            }
 
-            byte[] keyBlock = TlsUtilities.PRF(context, securityParameters.masterSecret, ExporterLabel.key_expansion,
-                TlsUtilities.Concat(securityParameters.serverRandom, securityParameters.clientRandom),
-                prfSize);
+            byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size);
 
             int offset = 0;
 
-            // Init MACs
-            wMac = CreateTlsMac(writeDigest, keyBlock, ref offset);
-            rMac = CreateTlsMac(readDigest, keyBlock, ref offset);
+            TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+                clientWriteDigest.GetDigestSize());
+            offset += clientWriteDigest.GetDigestSize();
+            TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+                serverWriteDigest.GetDigestSize());
+            offset += serverWriteDigest.GetDigestSize();
 
-            // Build keys
-            KeyParameter encryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize);
-            KeyParameter decryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize);
+            KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+            KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
 
-            // Add IVs
-            ParametersWithIV encryptParams = CreateParametersWithIV(encryptKey,
-                keyBlock, ref offset, encryptCipher.GetBlockSize());
-            ParametersWithIV decryptParams = CreateParametersWithIV(decryptKey,
-                keyBlock, ref offset, decryptCipher.GetBlockSize());
+            byte[] client_write_IV, server_write_IV;
+            if (useExplicitIV)
+            {
+                client_write_IV = new byte[clientWriteCipher.GetBlockSize()];
+                server_write_IV = new byte[serverWriteCipher.GetBlockSize()];
+            }
+            else
+            {
+                client_write_IV = Arrays.CopyOfRange(key_block, offset, offset + clientWriteCipher.GetBlockSize());
+                offset += clientWriteCipher.GetBlockSize();
+                server_write_IV = Arrays.CopyOfRange(key_block, offset, offset + serverWriteCipher.GetBlockSize());
+                offset += serverWriteCipher.GetBlockSize();
+            }
 
-            if (offset != prfSize)
+            if (offset != key_block_size)
+            {
                 throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
 
-            // Init Ciphers
-            encryptCipher.Init(true, encryptParams);
-            decryptCipher.Init(false, decryptParams);
-        }
+            ICipherParameters encryptParams, decryptParams;
+            if (context.IsServer)
+            {
+                this.mWriteMac = serverWriteMac;
+                this.mReadMac = clientWriteMac;
+                this.encryptCipher = serverWriteCipher;
+                this.decryptCipher = clientWriteCipher;
+                encryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+                decryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+            }
+            else
+            {
+                this.mWriteMac = clientWriteMac;
+                this.mReadMac = serverWriteMac;
+                this.encryptCipher = clientWriteCipher;
+                this.decryptCipher = serverWriteCipher;
+                encryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+                decryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+            }
 
-        protected virtual TlsMac CreateTlsMac(IDigest digest, byte[] buf, ref int off)
-        {
-            int len = digest.GetDigestSize();
-            TlsMac mac = new TlsMac(digest, buf, off, len);
-            off += len;
-            return mac;
+            this.encryptCipher.Init(true, encryptParams);
+            this.decryptCipher.Init(false, decryptParams);
         }
 
-        protected virtual KeyParameter CreateKeyParameter(byte[] buf, ref int off, int len)
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
         {
-            KeyParameter key = new KeyParameter(buf, off, len);
-            off += len;
-            return key;
-        }
+            int blockSize = encryptCipher.GetBlockSize();
+            int macSize = mWriteMac.Size;
 
-        protected virtual ParametersWithIV CreateParametersWithIV(KeyParameter key,
-            byte[] buf, ref int off, int len)
-        {
-            ParametersWithIV ivParams = new ParametersWithIV(key, buf, off, len);
-            off += len;
-            return ivParams;
+            int plaintextLimit = ciphertextLimit;
+
+            // An explicit IV consumes 1 block
+            if (useExplicitIV)
+            {
+                plaintextLimit -= blockSize;
+            }
+
+            // Leave room for the MAC, and require block-alignment
+            if (encryptThenMac)
+            {
+                plaintextLimit -= macSize;
+                plaintextLimit -= plaintextLimit % blockSize;
+            }
+            else
+            {
+                plaintextLimit -= plaintextLimit % blockSize;
+                plaintextLimit -= macSize;
+            }
+
+            // Minimum 1 byte of padding
+            --plaintextLimit;
+
+            return plaintextLimit;
         }
 
-        public virtual byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len)
+        public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len)
         {
-            int blocksize = encryptCipher.GetBlockSize();
-            int padding_length = blocksize - 1 - ((len + wMac.Size) % blocksize);
+            int blockSize = encryptCipher.GetBlockSize();
+            int macSize = mWriteMac.Size;
+
+            ProtocolVersion version = context.ServerVersion;
+
+            int enc_input_length = len;
+            if (!encryptThenMac)
+            {
+                enc_input_length += macSize;
+            }
 
-            //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion;
-            bool isTls = true;
+            int padding_length = blockSize - 1 - (enc_input_length % blockSize);
 
-            if (isTls)
+            // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though)
+            if (!version.IsDtls && !version.IsSsl)
             {
                 // Add a random number of extra blocks worth of padding
-                int maxExtraPadBlocks = (255 - padding_length) / blocksize;
+                int maxExtraPadBlocks = (255 - padding_length) / blockSize;
                 int actualExtraPadBlocks = ChooseExtraPadBlocks(context.SecureRandom, maxExtraPadBlocks);
-                padding_length += actualExtraPadBlocks * blocksize;
+                padding_length += actualExtraPadBlocks * blockSize;
+            }
+
+            int totalSize = len + macSize + padding_length + 1;
+            if (useExplicitIV)
+            {
+                totalSize += blockSize;
+            }
+
+            byte[] outBuf = new byte[totalSize];
+            int outOff = 0;
+
+            if (useExplicitIV)
+            {
+                byte[] explicitIV = new byte[blockSize];
+                context.NonceRandomGenerator.NextBytes(explicitIV);
+
+                encryptCipher.Init(true, new ParametersWithIV(null, explicitIV));
+
+                Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
+                outOff += blockSize;
+            }
+
+            int blocks_start = outOff;
+
+            Array.Copy(plaintext, offset, outBuf, outOff, len);
+            outOff += len;
+
+            if (!encryptThenMac)
+            {
+                byte[] mac = mWriteMac.CalculateMac(seqNo, type, plaintext, offset, len);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
             }
 
-            int totalsize = len + wMac.Size + padding_length + 1;
-            byte[] outbuf = new byte[totalsize];
-            Array.Copy(plaintext, offset, outbuf, 0, len);
-            byte[] mac = wMac.CalculateMac(type, plaintext, offset, len);
-            Array.Copy(mac, 0, outbuf, len, mac.Length);
-            int paddoffset = len + mac.Length;
             for (int i = 0; i <= padding_length; i++)
             {
-                outbuf[i + paddoffset] = (byte)padding_length;
+                outBuf[outOff++] = (byte)padding_length;
             }
-            for (int i = 0; i < totalsize; i += blocksize)
+
+            for (int i = blocks_start; i < outOff; i += blockSize)
+            {
+                encryptCipher.ProcessBlock(outBuf, i, outBuf, i);
+            }
+
+            if (encryptThenMac)
             {
-                encryptCipher.ProcessBlock(outbuf, i, outbuf, i);
+                byte[] mac = mWriteMac.CalculateMac(seqNo, type, outBuf, 0, outOff);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
             }
-            return outbuf;
+
+    //        assert outBuf.length == outOff;
+
+            return outBuf;
         }
 
-        public virtual byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len)
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len)
         {
             int blockSize = decryptCipher.GetBlockSize();
-            int macSize = rMac.Size;
+            int macSize = mReadMac.Size;
+
+            int minLen = blockSize;
+            if (encryptThenMac)
+            {
+                minLen += macSize;
+            }
+            else
+            {
+                minLen = System.Math.Max(minLen, macSize + 1);
+            }
 
-            /*
-             *  TODO[TLS 1.1] Explicit IV implies minLen = blockSize + max(blockSize, macSize + 1),
-             *  and will need further changes to offset and plen variables below.
-             */
+            if (useExplicitIV)
+            {
+                minLen += blockSize;
+            }
 
-            int minLen = System.Math.Max(blockSize, macSize + 1);
             if (len < minLen)
                 throw new TlsFatalAlert(AlertDescription.decode_error);
 
-            if (len % blockSize != 0)
+            int blocks_length = len;
+            if (encryptThenMac)
+            {
+                blocks_length -= macSize;
+            }
+
+            if (blocks_length % blockSize != 0)
                 throw new TlsFatalAlert(AlertDescription.decryption_failed);
 
-            for (int i = 0; i < len; i += blockSize)
+            if (encryptThenMac)
             {
-                decryptCipher.ProcessBlock(ciphertext, offset + i, ciphertext, offset + i);
+                int end = offset + len;
+                byte[] receivedMac = Arrays.CopyOfRange(ciphertext, end - macSize, end);
+                byte[] calculatedMac = mReadMac.CalculateMac(seqNo, type, ciphertext, offset, len - macSize);
+
+                bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
+
+                if (badMac)
+                    throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+            }
+
+            if (useExplicitIV)
+            {
+                decryptCipher.Init(false, new ParametersWithIV(null, ciphertext, offset, blockSize));
+
+                offset += blockSize;
+                blocks_length -= blockSize;
             }
 
-            int plen = len;
+            for (int i = 0; i < blocks_length; i += blockSize)
+            {
+                decryptCipher.ProcessBlock(ciphertext, offset + i, ciphertext, offset + i);
+            }
 
             // If there's anything wrong with the padding, this will return zero
-            int totalPad = CheckPaddingConstantTime(ciphertext, offset, plen, blockSize, macSize);
+            int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMac ? 0 : macSize);
 
-            int macInputLen = plen - totalPad - macSize;
+            int dec_output_length = blocks_length - totalPad;
 
-            byte[] decryptedMac = Arrays.Copy(ciphertext, offset + macInputLen, macSize);
-            byte[] calculatedMac = rMac.CalculateMacConstantTime(type, ciphertext, offset, macInputLen, plen - macSize, randomData);
+            if (!encryptThenMac)
+            {
+                dec_output_length -= macSize;
+                int macInputLen = dec_output_length;
+                int macOff = offset + macInputLen;
+                byte[] receivedMac = Arrays.CopyOfRange(ciphertext, macOff, macOff + macSize);
+                byte[] calculatedMac = mReadMac.CalculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen,
+                    blocks_length - macSize, randomData);
 
-            bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, decryptedMac);
+                bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
 
-            if (badMac || totalPad == 0)
-                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+                if (badMac || totalPad == 0)
+                {
+                    throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+                }
+            }
 
-            return Arrays.Copy(ciphertext, offset, macInputLen);
+            return Arrays.CopyOfRange(ciphertext, offset, offset + dec_output_length);
         }
 
         protected virtual int CheckPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize)
@@ -185,10 +316,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             int dummyIndex = 0;
             byte padDiff = 0;
 
-            //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion;
-            bool isTls = true;
-
-            if ((!isTls && totalPad > blockSize) || (macSize + totalPad > len))
+            if ((TlsUtilities.IsSsl(context) && totalPad > blockSize) || (macSize + totalPad > len))
             {
                 totalPad = 0;
             }
@@ -225,25 +353,24 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         protected virtual int ChooseExtraPadBlocks(SecureRandom r, int max)
         {
-//			return r.NextInt(max + 1);
+            // return r.NextInt(max + 1);
 
-            uint x = (uint)r.NextInt();
+            int x = r.NextInt();
             int n = LowestBitSet(x);
             return System.Math.Min(n, max);
         }
 
-        private int LowestBitSet(uint x)
+        protected virtual int LowestBitSet(int x)
         {
             if (x == 0)
-            {
                 return 32;
-            }
 
+            uint ux = (uint)x;
             int n = 0;
-            while ((x & 1) == 0)
+            while ((ux & 1U) == 0)
             {
                 ++n;
-                x >>= 1;
+                ux >>= 1;
             }
             return n;
         }
diff --git a/crypto/src/crypto/tls/TlsCipher.cs b/crypto/src/crypto/tls/TlsCipher.cs
index a58f4943f..7bd8573ac 100644
--- a/crypto/src/crypto/tls/TlsCipher.cs
+++ b/crypto/src/crypto/tls/TlsCipher.cs
@@ -5,10 +5,12 @@ namespace Org.BouncyCastle.Crypto.Tls
 {
     public interface TlsCipher
     {
+        int GetPlaintextLimit(int ciphertextLimit);
+
         /// <exception cref="IOException"></exception>
-        byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len);
+        byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len);
 
         /// <exception cref="IOException"></exception>
-        byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len);
+        byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len);
     }
 }
diff --git a/crypto/src/crypto/tls/TlsCipherFactory.cs b/crypto/src/crypto/tls/TlsCipherFactory.cs
index e5cf96479..4e1fe0eb9 100644
--- a/crypto/src/crypto/tls/TlsCipherFactory.cs
+++ b/crypto/src/crypto/tls/TlsCipherFactory.cs
@@ -6,7 +6,6 @@ namespace Org.BouncyCastle.Crypto.Tls
     public interface TlsCipherFactory
     {
         /// <exception cref="IOException"></exception>
-        TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm,
-            DigestAlgorithm digestAlgorithm);
+        TlsCipher CreateCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm);
     }
 }
diff --git a/crypto/src/crypto/tls/TlsHandshakeHash.cs b/crypto/src/crypto/tls/TlsHandshakeHash.cs
new file mode 100644
index 000000000..7118d9769
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsHandshakeHash.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public interface TlsHandshakeHash
+        :   IDigest
+    {
+        void Init(TlsContext context);
+
+        TlsHandshakeHash NotifyPrfDetermined();
+
+        void TrackHashAlgorithm(byte hashAlgorithm);
+
+        void SealHashAlgorithms();
+
+        TlsHandshakeHash StopTracking();
+
+        IDigest ForkPrfHash();
+
+        byte[] GetFinalHash(byte hashAlgorithm);
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsMac.cs b/crypto/src/crypto/tls/TlsMac.cs
index e4313617e..a80319a17 100644
--- a/crypto/src/crypto/tls/TlsMac.cs
+++ b/crypto/src/crypto/tls/TlsMac.cs
@@ -9,134 +9,165 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-    /// <remarks>
-    /// A generic TLS MAC implementation, which can be used with any kind of
-    /// IDigest to act as an HMAC.
-    /// </remarks>
+    /// <summary>
+    /// A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.
+    /// </summary>
     public class TlsMac
     {
-        protected long seqNo;
-        protected byte[] secret;
-        protected HMac mac;
+        protected readonly TlsContext context;
+        protected readonly byte[] secret;
+        protected readonly IMac mac;
+        protected readonly int digestBlockSize;
+        protected readonly int digestOverhead;
+        protected readonly int macLength;
 
         /**
-        * Generate a new instance of an TlsMac.
-        *
-        * @param digest    The digest to use.
-        * @param key_block A byte-array where the key for this mac is located.
-        * @param offset    The number of bytes to skip, before the key starts in the buffer.
-        * @param len       The length of the key.
-        */
-        public TlsMac(
-            IDigest	digest,
-            byte[]	key_block,
-            int		offset,
-            int		len)
+         * Generate a new instance of an TlsMac.
+         *
+         * @param context the TLS client context
+         * @param digest  The digest to use.
+         * @param key     A byte-array where the key for this MAC is located.
+         * @param keyOff  The number of bytes to skip, before the key starts in the buffer.
+         * @param keyLen  The length of the key.
+         */
+        public TlsMac(TlsContext context, IDigest digest, byte[] key, int keyOff, int keyLen)
         {
-            this.seqNo = 0;
+            this.context = context;
 
-            KeyParameter param = new KeyParameter(key_block, offset, len);
+            KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen);
 
-            this.secret = Arrays.Clone(param.GetKey());
+            this.secret = Arrays.Clone(keyParameter.GetKey());
 
-            this.mac = new HMac(digest);
-            this.mac.Init(param);
-        }
+            // TODO This should check the actual algorithm, not rely on the engine type
+            if (digest is LongDigest)
+            {
+                this.digestBlockSize = 128;
+                this.digestOverhead = 16;
+            }
+            else
+            {
+                this.digestBlockSize = 64;
+                this.digestOverhead = 8;
+            }
 
-        /**
-         * @return the MAC write secret
-         */
-        public virtual byte[] GetMacSecret()
-        {
-            return this.secret;
-        }
+            if (TlsUtilities.IsSsl(context))
+            {
+                this.mac = new Ssl3Mac(digest);
 
-        /**
-         * @return the current write sequence number
-         */
-        public virtual long SequenceNumber
-        {
-            get { return this.seqNo; }
+                // TODO This should check the actual algorithm, not assume based on the digest size
+                if (digest.GetDigestSize() == 20)
+                {
+                    /*
+                     * NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not
+                     * digest block-aligned.
+                     */
+                    this.digestOverhead = 4;
+                }
+            }
+            else
+            {
+                this.mac = new HMac(digest);
+
+                // NOTE: The input pad for HMAC is always a full digest block
+            }
+
+            this.mac.Init(keyParameter);
+
+            this.macLength = mac.GetMacSize();
+            if (context.SecurityParameters.truncatedHMac)
+            {
+                this.macLength = System.Math.Min(this.macLength, 10);
+            }
         }
 
         /**
-         * Increment the current write sequence number
+         * @return the MAC write secret
          */
-        public virtual void IncSequenceNumber()
+        public virtual byte[] MacSecret
         {
-            this.seqNo++;
+            get { return this.secret; }
         }
 
         /**
-        * @return The Keysize of the mac.
-        */
+         * @return The output length of this MAC.
+         */
         public virtual int Size
         {
-            get { return mac.GetMacSize(); }
+            get { return macLength; }
         }
 
         /**
-        * Calculate the mac for some given data.
-        * <p/>
-        * TlsMac will keep track of the sequence number internally.
-        *
-        * @param type    The message type of the message.
-        * @param message A byte-buffer containing the message.
-        * @param offset  The number of bytes to skip, before the message starts.
-        * @param len     The length of the message.
-        * @return A new byte-buffer containing the mac value.
-        */
-        public virtual byte[] CalculateMac(byte type, byte[] message, int offset, int len)
+         * Calculate the MAC for some given data.
+         *
+         * @param type    The message type of the message.
+         * @param message A byte-buffer containing the message.
+         * @param offset  The number of bytes to skip, before the message starts.
+         * @param length  The length of the message.
+         * @return A new byte-buffer containing the MAC value.
+         */
+        public virtual byte[] CalculateMac(long seqNo, byte type, byte[] message, int offset, int length)
         {
-            //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion;
-            bool isTls = true;
+            ProtocolVersion serverVersion = context.ServerVersion;
+            bool isSsl = serverVersion.IsSsl;
 
-            byte[] macHeader = new byte[isTls ? 13 : 11];
-            TlsUtilities.WriteUint64(seqNo++, macHeader, 0);
+            byte[] macHeader = new byte[isSsl ? 11 : 13];
+            TlsUtilities.WriteUint64(seqNo, macHeader, 0);
             TlsUtilities.WriteUint8(type, macHeader, 8);
-            if (isTls)
+            if (!isSsl)
             {
-                TlsUtilities.WriteVersion(macHeader, 9);
+                TlsUtilities.WriteVersion(serverVersion, macHeader, 9);
             }
-            TlsUtilities.WriteUint16(len, macHeader, 11);
+            TlsUtilities.WriteUint16(length, macHeader, macHeader.Length - 2);
 
             mac.BlockUpdate(macHeader, 0, macHeader.Length);
-            mac.BlockUpdate(message, offset, len);
-            return MacUtilities.DoFinal(mac);
+            mac.BlockUpdate(message, offset, length);
+
+            return Truncate(MacUtilities.DoFinal(mac));
         }
 
-        public virtual byte[] CalculateMacConstantTime(byte type, byte[] message, int offset, int len,
+        public virtual byte[] CalculateMacConstantTime(long seqNo, byte type, byte[] message, int offset, int length,
             int fullLength, byte[] dummyData)
         {
-            // Actual MAC only calculated on 'len' bytes
-            byte[] result = CalculateMac(type, message, offset, len);
+            /*
+             * Actual MAC only calculated on 'length' bytes...
+             */
+            byte[] result = CalculateMac(seqNo, type, message, offset, length);
+
+            /*
+             * ...but ensure a constant number of complete digest blocks are processed (as many as would
+             * be needed for 'fullLength' bytes of input).
+             */
+            int headerLength = TlsUtilities.IsSsl(context) ? 11 : 13;
 
-            //bool isTls = context.ServerVersion.FullVersion >= ProtocolVersion.TLSv10.FullVersion;
-            bool isTls = true;
+            // How many extra full blocks do we need to calculate?
+            int extra = GetDigestBlockCount(headerLength + fullLength) - GetDigestBlockCount(headerLength + length);
 
-            // ...but ensure a constant number of complete digest blocks are processed (per 'fullLength')
-            if (isTls)
+            while (--extra >= 0)
             {
-                // TODO Currently all TLS digests use a block size of 64, a suffix (length field) of 8, and padding (1+)
-                int db = 64, ds = 8;
+                mac.BlockUpdate(dummyData, 0, digestBlockSize);
+            }
 
-                int L1 = 13 + fullLength;
-                int L2 = 13 + len;
+            // One more byte in case the implementation is "lazy" about processing blocks
+            mac.Update(dummyData[0]);
+            mac.Reset();
 
-                // How many extra full blocks do we need to calculate?
-                int extra = ((L1 + ds) / db) - ((L2 + ds) / db);
+            return result;
+        }
 
-                while (--extra >= 0)
-                {
-                    mac.BlockUpdate(dummyData, 0, db);
-                }
+        protected virtual int GetDigestBlockCount(int inputLength)
+        {
+            // NOTE: This calculation assumes a minimum of 1 pad byte
+            return (inputLength + digestOverhead) / digestBlockSize;
+        }
 
-                // One more byte in case the implementation is "lazy" about processing blocks
-                mac.Update(dummyData[0]);
-                mac.Reset();
+        protected virtual byte[] Truncate(byte[] bs)
+        {
+            if (bs.Length <= macLength)
+            {
+                return bs;
             }
 
-            return result;
+            return Arrays.CopyOf(bs, macLength);
         }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsNullCipher.cs b/crypto/src/crypto/tls/TlsNullCipher.cs
index 3e2bfa847..f30ace24f 100644
--- a/crypto/src/crypto/tls/TlsNullCipher.cs
+++ b/crypto/src/crypto/tls/TlsNullCipher.cs
@@ -1,28 +1,118 @@
 using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
     /// <summary>
-    /// A NULL cipher suite, for use during handshake.
+    /// A NULL CipherSuite, with optional MAC.
     /// </summary>
     public class TlsNullCipher
-        : TlsCipher
+        :   TlsCipher
     {
-        public virtual byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len)
+        protected readonly TlsContext context;
+
+        protected readonly TlsMac writeMac;
+        protected readonly TlsMac readMac;
+
+        public TlsNullCipher(TlsContext context)
         {
-            return CopyData(plaintext, offset, len);
+            this.context = context;
+            this.writeMac = null;
+            this.readMac = null;
         }
 
-        public virtual byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len)
+        /// <exception cref="IOException"></exception>
+        public TlsNullCipher(TlsContext context, IDigest clientWriteDigest, IDigest serverWriteDigest)
         {
-            return CopyData(ciphertext, offset, len);
+            if ((clientWriteDigest == null) != (serverWriteDigest == null))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.context = context;
+
+            TlsMac clientWriteMac = null, serverWriteMac = null;
+
+            if (clientWriteDigest != null)
+            {
+                int key_block_size = clientWriteDigest.GetDigestSize()
+                    + serverWriteDigest.GetDigestSize();
+                byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size);
+
+                int offset = 0;
+
+                clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+                    clientWriteDigest.GetDigestSize());
+                offset += clientWriteDigest.GetDigestSize();
+
+                serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+                    serverWriteDigest.GetDigestSize());
+                offset += serverWriteDigest.GetDigestSize();
+
+                if (offset != key_block_size)
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+            }
+
+            if (context.IsServer)
+            {
+                writeMac = serverWriteMac;
+                readMac = clientWriteMac;
+            }
+            else
+            {
+                writeMac = clientWriteMac;
+                readMac = serverWriteMac;
+            }
         }
 
-        protected virtual byte[] CopyData(byte[] text, int offset, int len)
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
         {
-            byte[] result = new byte[len];
-            Array.Copy(text, offset, result, 0, len);
+            int result = ciphertextLimit;
+            if (writeMac != null)
+            {
+                result -= writeMac.Size;
+            }
             return result;
         }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len)
+        {
+            if (writeMac == null)
+            {
+                return Arrays.CopyOfRange(plaintext, offset, offset + len);
+            }
+
+            byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len);
+            byte[] ciphertext = new byte[len + mac.Length];
+            Array.Copy(plaintext, offset, ciphertext, 0, len);
+            Array.Copy(mac, 0, ciphertext, len, mac.Length);
+            return ciphertext;
+        }
+
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len)
+        {
+            if (readMac == null)
+            {
+                return Arrays.CopyOfRange(ciphertext, offset, offset + len);
+            }
+
+            int macSize = readMac.Size;
+            if (len < macSize)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int macInputLen = len - macSize;
+
+            byte[] receivedMac = Arrays.CopyOfRange(ciphertext, offset + macInputLen, offset + len);
+            byte[] computedMac = readMac.CalculateMac(seqNo, type, ciphertext, offset, macInputLen);
+
+            if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac))
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
+            return Arrays.CopyOfRange(ciphertext, offset, offset + macInputLen);
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsProtocolHandler.cs b/crypto/src/crypto/tls/TlsProtocolHandler.cs
index 918640db5..3dec5ade0 100644
--- a/crypto/src/crypto/tls/TlsProtocolHandler.cs
+++ b/crypto/src/crypto/tls/TlsProtocolHandler.cs
@@ -54,7 +54,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         /*
         * The Record Stream we use
         */
-        private RecordStream rs;
+        private RecordStream recordStream;
         private SecureRandom random;
 
         private TlsStream tlsStream = null;
@@ -116,7 +116,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             Stream			outStr,
             SecureRandom	sr)
         {
-            this.rs = new RecordStream(this, inStr, outStr);
+            this.recordStream = new RecordStream(this, inStr, outStr);
             this.random = sr;
         }
 
@@ -202,8 +202,8 @@ namespace Org.BouncyCastle.Crypto.Tls
                             case HandshakeType.finished:
                                 break;
                             default:
-                                rs.UpdateHandshakeData(beginning, 0, 4);
-                                rs.UpdateHandshakeData(buf, 0, len);
+                                recordStream.UpdateHandshakeData(beginning, 0, 4);
+                                recordStream.UpdateHandshakeData(buf, 0, len);
                                 break;
                         }
 
@@ -270,7 +270,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                              * Calculate our own checksum.
                              */
                             byte[] expectedServerVerifyData = TlsUtilities.PRF(tlsClientContext,
-                                securityParameters.masterSecret, ExporterLabel.server_finished, rs.GetCurrentHash(), 12);
+                                securityParameters.masterSecret, ExporterLabel.server_finished, recordStream.GetCurrentHash(), 12);
 
                             /*
                              * Compare both checksums.
@@ -445,6 +445,9 @@ namespace Org.BouncyCastle.Crypto.Tls
                                 tlsClient.NotifySecureRenegotiation(secure_negotiation);
                             }
 
+                            this.securityParameters.cipherSuite = selectedCipherSuite;
+                            this.securityParameters.compressionAlgorithm = selectedCompressionMethod;
+
                             if (clientExtensions != null)
                             {
                                 tlsClient.ProcessServerExtensions(serverExtensions);
@@ -457,6 +460,13 @@ namespace Org.BouncyCastle.Crypto.Tls
                             // TODO Just a place-holder until other TLS 1.2 changes arrive
                             this.securityParameters.prfAlgorithm = PrfAlgorithm.tls_prf_legacy;
 
+                            /*
+                             * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
+                             * verify_data_length has a verify_data_length equal to 12. This includes all
+                             * existing cipher suites.
+                             */
+                            this.securityParameters.verifyDataLength = 12;
+
                             break;
                         default:
                             this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
@@ -527,7 +537,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                             if (clientCreds != null && clientCreds is TlsSignerCredentials)
                             {
                                 TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds;
-                                byte[] md5andsha1 = rs.GetCurrentHash();
+                                byte[] md5andsha1 = recordStream.GetCurrentHash();
                                 byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature(
                                     md5andsha1);
                                 SendCertificateVerify(clientCertificateSignature);
@@ -540,7 +550,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                             */
                             byte[] cmessage = new byte[1];
                             cmessage[0] = 1;
-                            rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length);
+                            recordStream.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length);
 
                             connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;
 
@@ -562,20 +572,20 @@ namespace Org.BouncyCastle.Crypto.Tls
                             /*
                              * Initialize our cipher suite
                              */
-                            rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());
+                            recordStream.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());
 
                             /*
                              * Send our finished message.
                              */
                             byte[] clientVerifyData = TlsUtilities.PRF(tlsClientContext, securityParameters.masterSecret,
-                                ExporterLabel.client_finished, rs.GetCurrentHash(), 12);
+                                ExporterLabel.client_finished, recordStream.GetCurrentHash(), 12);
 
                             MemoryStream bos = new MemoryStream();
                             TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos);
                             TlsUtilities.WriteOpaque24(clientVerifyData, bos);
                             byte[] message = bos.ToArray();
 
-                            rs.WriteMessage(ContentType.handshake, message, 0, message.Length);
+                            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
 
                             this.connection_state = CS_CLIENT_FINISHED_SEND;
                             break;
@@ -703,7 +713,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                     */
                     try
                     {
-                        rs.Close();
+                        recordStream.Close();
                     }
                     catch (Exception)
                     {
@@ -747,7 +757,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                     this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                 }
 
-                rs.ServerClientSpecReceived();
+                recordStream.ServerClientSpecReceived();
 
                 this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
             }
@@ -767,7 +777,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             // Patch actual length back in
             TlsUtilities.WriteUint24(message.Length - 4, message, 1);
 
-            rs.WriteMessage(ContentType.handshake, message, 0, message.Length);
+            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
         }
 
         private void SendClientKeyExchange()
@@ -784,7 +794,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             // Patch actual length back in
             TlsUtilities.WriteUint24(message.Length - 4, message, 1);
 
-            rs.WriteMessage(ContentType.handshake, message, 0, message.Length);
+            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
         }
 
         private void SendCertificateVerify(byte[] data)
@@ -799,7 +809,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             TlsUtilities.WriteOpaque16(data, bos);
             byte[] message = bos.ToArray();
 
-            rs.WriteMessage(ContentType.handshake, message, 0, message.Length);
+            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
         }
 
         /// <summary>Connects to the remote system.</summary>
@@ -823,6 +833,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             this.tlsClient = tlsClient;
 
             this.securityParameters = new SecurityParameters();
+            this.securityParameters.entity = ConnectionEnd.client;
 
             this.tlsClientContext = new TlsClientContextImpl(random, securityParameters);
 
@@ -830,6 +841,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                 tlsClientContext.NonceRandomGenerator);
 
             this.tlsClient.Init(tlsClientContext);
+            this.recordStream.Init(tlsClientContext);
 
             /*
              * Send Client hello
@@ -965,7 +977,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             try
             {
-                rs.ReadData();
+                recordStream.ReadData();
             }
             catch (TlsFatalAlert e)
             {
@@ -997,7 +1009,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             try
             {
-                rs.WriteMessage(type, buf, offset, len);
+                recordStream.WriteMessage(type, buf, offset, len);
             }
             catch (TlsFatalAlert e)
             {
@@ -1127,7 +1139,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                     this.failedWithError = true;
                 }
                 SendAlert(alertLevel, alertDescription);
-                rs.Close();
+                recordStream.Close();
                 if (alertLevel == AlertLevel.fatal)
                 {
                     throw new IOException(TLS_ERROR_MESSAGE);
@@ -1143,7 +1155,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             byte[] error = new byte[] { alertLevel, alertDescription };
 
-            rs.WriteMessage(ContentType.alert, error, 0, 2);
+            recordStream.WriteMessage(ContentType.alert, error, 0, 2);
         }
 
         /// <summary>Closes this connection</summary>
@@ -1180,7 +1192,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         internal void Flush()
         {
-            rs.Flush();
+            recordStream.Flush();
         }
 
         internal bool IsClosed
diff --git a/crypto/src/crypto/tls/TlsStreamCipher.cs b/crypto/src/crypto/tls/TlsStreamCipher.cs
index 24e2ce73f..d664b09dd 100644
--- a/crypto/src/crypto/tls/TlsStreamCipher.cs
+++ b/crypto/src/crypto/tls/TlsStreamCipher.cs
@@ -1,108 +1,173 @@
 using System;
+using System.IO;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Tls;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-    public class TlsStreamCipher : TlsCipher
+    public class TlsStreamCipher
+        :   TlsCipher
     {
-        protected TlsContext context;
+        protected readonly TlsContext context;
 
-        protected IStreamCipher encryptCipher;
-        protected IStreamCipher decryptCipher;
+        protected readonly IStreamCipher encryptCipher;
+        protected readonly IStreamCipher decryptCipher;
 
-        protected TlsMac writeMac;
-        protected TlsMac readMac;
+        protected readonly TlsMac writeMac;
+        protected readonly TlsMac readMac;
 
-        public TlsStreamCipher(TlsContext context, IStreamCipher encryptCipher,
-            IStreamCipher decryptCipher, IDigest writeDigest, IDigest readDigest, int cipherKeySize)
+        protected readonly bool usesNonce;
+
+        /// <exception cref="IOException"></exception>
+        [Obsolete("Use version with additional 'usesNonce' argument")]
+        public TlsStreamCipher(TlsContext context, IStreamCipher clientWriteCipher,
+            IStreamCipher serverWriteCipher, IDigest clientWriteDigest, IDigest serverWriteDigest,
+            int cipherKeySize)
+            :   this(context, clientWriteCipher, serverWriteCipher, clientWriteDigest, serverWriteDigest, cipherKeySize, false)
         {
+        }
+
+        /// <exception cref="IOException"></exception>
+        public TlsStreamCipher(TlsContext context, IStreamCipher clientWriteCipher,
+            IStreamCipher serverWriteCipher, IDigest clientWriteDigest, IDigest serverWriteDigest,
+            int cipherKeySize, bool usesNonce)
+        {
+            bool isServer = context.IsServer;
+
             this.context = context;
-            this.encryptCipher = encryptCipher;
-            this.decryptCipher = decryptCipher;
+            this.usesNonce = usesNonce;
 
-            int prfSize = (2 * cipherKeySize) + writeDigest.GetDigestSize()
-                + readDigest.GetDigestSize();
+            this.encryptCipher = clientWriteCipher;
+            this.decryptCipher = serverWriteCipher;
 
-            SecurityParameters securityParameters = context.SecurityParameters;
+            int key_block_size = (2 * cipherKeySize) + clientWriteDigest.GetDigestSize()
+                + serverWriteDigest.GetDigestSize();
 
-            byte[] keyBlock = TlsUtilities.PRF(context, securityParameters.masterSecret, ExporterLabel.key_expansion,
-                TlsUtilities.Concat(securityParameters.serverRandom, securityParameters.clientRandom),
-                prfSize);
+            byte[] key_block = TlsUtilities.CalculateKeyBlock(context, key_block_size);
 
             int offset = 0;
 
             // Init MACs
-            writeMac = CreateTlsMac(writeDigest, keyBlock, ref offset);
-            readMac = CreateTlsMac(readDigest, keyBlock, ref offset);
+            TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+                clientWriteDigest.GetDigestSize());
+            offset += clientWriteDigest.GetDigestSize();
+            TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+                serverWriteDigest.GetDigestSize());
+            offset += serverWriteDigest.GetDigestSize();
 
             // Build keys
-            KeyParameter encryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize);
-            KeyParameter decryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize);
+            KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+            KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
 
-            if (offset != prfSize)
+            if (offset != key_block_size)
+            {
                 throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
 
-            // Init Ciphers
-            encryptCipher.Init(true, encryptKey);
-            decryptCipher.Init(false, decryptKey);
-        }
-
-        public byte[] EncodePlaintext(byte type, byte[] plaintext, int offset, int len)
-        {
-            byte[] mac = writeMac.CalculateMac(type, plaintext, offset, len);
-            int size = len + mac.Length;
+            ICipherParameters encryptParams, decryptParams;
+            if (isServer)
+            {
+                this.writeMac = serverWriteMac;
+                this.readMac = clientWriteMac;
+                this.encryptCipher = serverWriteCipher;
+                this.decryptCipher = clientWriteCipher;
+                encryptParams = serverWriteKey;
+                decryptParams = clientWriteKey;
+            }
+            else
+            {
+                this.writeMac = clientWriteMac;
+                this.readMac = serverWriteMac;
+                this.encryptCipher = clientWriteCipher;
+                this.decryptCipher = serverWriteCipher;
+                encryptParams = clientWriteKey;
+                decryptParams = serverWriteKey;
+            }
 
-            byte[] outbuf = new byte[size];
+            if (usesNonce)
+            {
+                byte[] dummyNonce = new byte[8];
+                encryptParams = new ParametersWithIV(encryptParams, dummyNonce);
+                decryptParams = new ParametersWithIV(decryptParams, dummyNonce);
+            }
 
-            encryptCipher.ProcessBytes(plaintext, offset, len, outbuf, 0);
-            encryptCipher.ProcessBytes(mac, 0, mac.Length, outbuf, len);
+            this.encryptCipher.Init(true, encryptParams);
+            this.decryptCipher.Init(false, decryptParams);
+        }
 
-            return outbuf;
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            return ciphertextLimit - writeMac.Size;
         }
 
-        public byte[] DecodeCiphertext(byte type, byte[] ciphertext, int offset, int len)
+        public virtual byte[] EncodePlaintext(long seqNo, byte type, byte[] plaintext, int offset, int len)
         {
-            byte[] deciphered = new byte[len];
-            decryptCipher.ProcessBytes(ciphertext, offset, len, deciphered, 0);
+            /*
+             * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That
+             * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS
+             * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation
+             * of the 16-bit epoch with the 48-bit sequence number.
+             */
+            if (usesNonce)
+            {
+                UpdateIV(encryptCipher, true, seqNo);
+            }
 
-            int plaintextSize = deciphered.Length - readMac.Size;
-            byte[] plainText = CopyData(deciphered, 0, plaintextSize);
+            byte[] outBuf = new byte[len + writeMac.Size];
 
-            byte[] receivedMac = CopyData(deciphered, plaintextSize, readMac.Size);
-            byte[] computedMac = readMac.CalculateMac(type, plainText, 0, plainText.Length);
+            encryptCipher.ProcessBytes(plaintext, offset, len, outBuf, 0);
 
-            if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac))
-            {
-                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
-            }
+            byte[] mac = writeMac.CalculateMac(seqNo, type, plaintext, offset, len);
+            encryptCipher.ProcessBytes(mac, 0, mac.Length, outBuf, len);
 
-            return plainText;
+            return outBuf;
         }
 
-        protected virtual TlsMac CreateTlsMac(IDigest digest, byte[] buf, ref int off)
+        /// <exception cref="IOException"></exception>
+        public virtual byte[] DecodeCiphertext(long seqNo, byte type, byte[] ciphertext, int offset, int len)
         {
-            int len = digest.GetDigestSize();
-            TlsMac mac = new TlsMac(digest, buf, off, len);
-            off += len;
-            return mac;
+            /*
+             * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That
+             * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS
+             * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation
+             * of the 16-bit epoch with the 48-bit sequence number.
+             */
+            if (usesNonce)
+            {
+                UpdateIV(decryptCipher, false, seqNo);
+            }
+
+            int macSize = readMac.Size;
+            if (len < macSize)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int plaintextLength = len - macSize;
+
+            byte[] deciphered = new byte[len];
+            decryptCipher.ProcessBytes(ciphertext, offset, len, deciphered, 0);
+            CheckMac(seqNo, type, deciphered, plaintextLength, len, deciphered, 0, plaintextLength);
+            return Arrays.CopyOfRange(deciphered, 0, plaintextLength);
         }
 
-        protected virtual KeyParameter CreateKeyParameter(byte[] buf, ref int off, int len)
+        /// <exception cref="IOException"></exception>
+        protected virtual void CheckMac(long seqNo, byte type, byte[] recBuf, int recStart, int recEnd, byte[] calcBuf, int calcOff, int calcLen)
         {
-            KeyParameter key = new KeyParameter(buf, off, len);
-            off += len;
-            return key;
+            byte[] receivedMac = Arrays.CopyOfRange(recBuf, recStart, recEnd);
+            byte[] computedMac = readMac.CalculateMac(seqNo, type, calcBuf, calcOff, calcLen);
+
+            if (!Arrays.ConstantTimeAreEqual(receivedMac, computedMac))
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
         }
 
-        protected virtual byte[] CopyData(byte[] text, int offset, int len)
+        protected virtual void UpdateIV(IStreamCipher cipher, bool forEncryption, long seqNo)
         {
-            byte[] result = new byte[len];
-            Array.Copy(text, offset, result, 0, len);
-            return result;
+            byte[] nonce = new byte[8];
+            TlsUtilities.WriteUint64(seqNo, nonce, 0);
+            cipher.Init(forEncryption, new ParametersWithIV(null, nonce));
         }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs
index 462ec4074..bd5362e5b 100644
--- a/crypto/src/crypto/tls/TlsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsUtilities.cs
@@ -10,6 +10,7 @@ using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Macs;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Date;
 using Org.BouncyCastle.Utilities.IO;
@@ -759,6 +760,105 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
+        internal static byte[] CalculateKeyBlock(TlsContext context, int size)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            byte[] master_secret = securityParameters.MasterSecret;
+            byte[] seed = Concat(securityParameters.ServerRandom, securityParameters.ClientRandom);
+
+            if (IsSsl(context))
+            {
+                return CalculateKeyBlock_Ssl(master_secret, seed, size);
+            }
+
+            return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size);
+        }
+
+        internal static byte[] CalculateKeyBlock_Ssl(byte[] master_secret, byte[] random, int size)
+        {
+            IDigest md5 = CreateHash(HashAlgorithm.md5);
+            IDigest sha1 = CreateHash(HashAlgorithm.sha1);
+            int md5Size = md5.GetDigestSize();
+            byte[] shatmp = new byte[sha1.GetDigestSize()];
+            byte[] tmp = new byte[size + md5Size];
+
+            int i = 0, pos = 0;
+            while (pos < size)
+            {
+                byte[] ssl3Const = SSL3_CONST[i];
+
+                sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length);
+                sha1.BlockUpdate(master_secret, 0, master_secret.Length);
+                sha1.BlockUpdate(random, 0, random.Length);
+                sha1.DoFinal(shatmp, 0);
+
+                md5.BlockUpdate(master_secret, 0, master_secret.Length);
+                md5.BlockUpdate(shatmp, 0, shatmp.Length);
+                md5.DoFinal(tmp, pos);
+
+                pos += md5Size;
+                ++i;
+            }
+
+            return Arrays.CopyOfRange(tmp, 0, size);
+        }
+
+        internal static byte[] CalculateMasterSecret(TlsContext context, byte[] pre_master_secret)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            byte[] seed = Concat(securityParameters.ClientRandom, securityParameters.ServerRandom);
+
+            if (IsSsl(context))
+            {
+                return CalculateMasterSecret_Ssl(pre_master_secret, seed);
+            }
+
+            return PRF(context, pre_master_secret, ExporterLabel.master_secret, seed, 48);
+        }
+
+        internal static byte[] CalculateMasterSecret_Ssl(byte[] pre_master_secret, byte[] random)
+        {
+            IDigest md5 = CreateHash(HashAlgorithm.md5);
+            IDigest sha1 = CreateHash(HashAlgorithm.sha1);
+            int md5Size = md5.GetDigestSize();
+            byte[] shatmp = new byte[sha1.GetDigestSize()];
+
+            byte[] rval = new byte[md5Size * 3];
+            int pos = 0;
+
+            for (int i = 0; i < 3; ++i)
+            {
+                byte[] ssl3Const = SSL3_CONST[i];
+
+                sha1.BlockUpdate(ssl3Const, 0, ssl3Const.Length);
+                sha1.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length);
+                sha1.BlockUpdate(random, 0, random.Length);
+                sha1.DoFinal(shatmp, 0);
+
+                md5.BlockUpdate(pre_master_secret, 0, pre_master_secret.Length);
+                md5.BlockUpdate(shatmp, 0, shatmp.Length);
+                md5.DoFinal(rval, pos);
+
+                pos += md5Size;
+            }
+
+            return rval;
+        }
+
+        internal static byte[] CalculateVerifyData(TlsContext context, string asciiLabel, byte[] handshakeHash)
+        {
+            if (IsSsl(context))
+            {
+                return handshakeHash;
+            }
+
+            SecurityParameters securityParameters = context.SecurityParameters;
+            byte[] master_secret = securityParameters.MasterSecret;
+            int verify_data_length = securityParameters.VerifyDataLength;
+
+            return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length);
+        }
+
         public static IDigest CreateHash(byte hashAlgorithm)
         {
             switch (hashAlgorithm)
@@ -859,6 +959,130 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
+        internal static short GetClientCertificateType(Certificate clientCertificate, Certificate serverCertificate)
+        {
+            if (clientCertificate.IsEmpty)
+                return -1;
+
+            X509CertificateStructure x509Cert = clientCertificate.GetCertificateAt(0);
+            SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo;
+            try
+            {
+                AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo);
+                if (publicKey.IsPrivate)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                /*
+                 * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/
+                 * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the
+                 * constraints on certificate-signing algorithms found in prior versions of TLS.
+                 */
+
+                /*
+                 * RFC 5246 7.4.6. Client Certificate
+                 */
+
+                /*
+                 * RSA public key; the certificate MUST allow the key to be used for signing with the
+                 * signature scheme and hash algorithm that will be employed in the certificate verify
+                 * message.
+                 */
+                if (publicKey is RsaKeyParameters)
+                {
+                    ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature);
+                    return ClientCertificateType.rsa_sign;
+                }
+
+                /*
+                 * DSA public key; the certificate MUST allow the key to be used for signing with the
+                 * hash algorithm that will be employed in the certificate verify message.
+                 */
+                if (publicKey is DsaPublicKeyParameters)
+                {
+                    ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature);
+                    return ClientCertificateType.dss_sign;
+                }
+
+                /*
+                 * ECDSA-capable public key; the certificate MUST allow the key to be used for signing
+                 * with the hash algorithm that will be employed in the certificate verify message; the
+                 * public key MUST use a curve and point format supported by the server.
+                 */
+                if (publicKey is ECPublicKeyParameters)
+                {
+                    ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature);
+                    // TODO Check the curve and point format
+                    return ClientCertificateType.ecdsa_sign;
+                }
+
+                // TODO Add support for ClientCertificateType.*_fixed_*
+            }
+            catch (Exception)
+            {
+            }
+
+            throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+        }
+
+        internal static void TrackHashAlgorithms(TlsHandshakeHash handshakeHash, IList supportedSignatureAlgorithms)
+        {
+            if (supportedSignatureAlgorithms != null)
+            {
+                foreach (SignatureAndHashAlgorithm signatureAndHashAlgorithm in supportedSignatureAlgorithms)
+                {
+                    byte hashAlgorithm = signatureAndHashAlgorithm.Hash;
+                    handshakeHash.TrackHashAlgorithm(hashAlgorithm);
+                }
+            }
+        }
+
+        public static bool HasSigningCapability(byte clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.ecdsa_sign:
+            case ClientCertificateType.rsa_sign:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        public static TlsSigner CreateTlsSigner(byte clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case ClientCertificateType.dss_sign:
+                return new TlsDssSigner();
+            case ClientCertificateType.ecdsa_sign:
+                return new TlsECDsaSigner();
+            case ClientCertificateType.rsa_sign:
+                return new TlsRsaSigner();
+            default:
+                throw new ArgumentException("not a type with signing capability", "clientCertificateType");
+            }
+        }
+
+        internal static readonly byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54};
+        internal static readonly byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52};
+
+        // SSL3 magic mix constants ("A", "BB", "CCC", ...)
+        internal static readonly byte[][] SSL3_CONST = GenSsl3Const();
+
+        private static byte[][] GenSsl3Const()
+        {
+            int n = 10;
+            byte[][] arr = new byte[n][];
+            for (int i = 0; i < n; i++)
+            {
+                byte[] b = new byte[i + 1];
+                Arrays.Fill(b, (byte)('A' + i));
+                arr[i] = b;
+            }
+            return arr;
+        }
+
         private static IList VectorOfOne(object obj)
         {
             IList v = Platform.CreateArrayList(1);
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index e629fcf65..87bc1ba65 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -1,6 +1,8 @@
 using System;
 using System.Text;
 
+using Org.BouncyCastle.Math;
+
 namespace Org.BouncyCastle.Utilities
 {
     /// <summary> General array utilities.</summary>
@@ -407,11 +409,90 @@ namespace Org.BouncyCastle.Utilities
             }
         }
 
-        public static byte[] Copy(byte[] data, int off, int len)
+        public static byte[] CopyOf(byte[] data, int newLength)
         {
-            byte[] result = new byte[len];
-            Array.Copy(data, off, result, 0, len);
-            return result;
+            byte[] tmp = new byte[newLength];
+            Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length));
+            return tmp;
+        }
+
+        public static char[] CopyOf(char[] data, int newLength)
+        {
+            char[] tmp = new char[newLength];
+            Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length));
+            return tmp;
+        }
+
+        public static int[] CopyOf(int[] data, int newLength)
+        {
+            int[] tmp = new int[newLength];
+            Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length));
+            return tmp;
+        }
+
+        public static long[] CopyOf(long[] data, int newLength)
+        {
+            long[] tmp = new long[newLength];
+            Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length));
+            return tmp;
+        }
+
+        public static BigInteger[] CopyOf(BigInteger[] data, int newLength)
+        {
+            BigInteger[] tmp = new BigInteger[newLength];
+            Array.Copy(data, 0, tmp, 0, System.Math.Min(newLength, data.Length));
+            return tmp;
+        }
+
+        /**
+         * Make a copy of a range of bytes from the passed in data array. The range can
+         * extend beyond the end of the input array, in which case the return array will
+         * be padded with zeroes.
+         *
+         * @param data the array from which the data is to be copied.
+         * @param from the start index at which the copying should take place.
+         * @param to the final index of the range (exclusive).
+         *
+         * @return a new byte array containing the range given.
+         */
+        public static byte[] CopyOfRange(byte[] data, int from, int to)
+        {
+            int newLength = GetLength(from, to);
+            byte[] tmp = new byte[newLength];
+            Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from));
+            return tmp;
+        }
+
+        public static int[] CopyOfRange(int[] data, int from, int to)
+        {
+            int newLength = GetLength(from, to);
+            int[] tmp = new int[newLength];
+            Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from));
+            return tmp;
+        }
+
+        public static long[] CopyOfRange(long[] data, int from, int to)
+        {
+            int newLength = GetLength(from, to);
+            long[] tmp = new long[newLength];
+            Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from));
+            return tmp;
+        }
+
+        public static BigInteger[] CopyOfRange(BigInteger[] data, int from, int to)
+        {
+            int newLength = GetLength(from, to);
+            BigInteger[] tmp = new BigInteger[newLength];
+            Array.Copy(data, from, tmp, 0, System.Math.Min(newLength, data.Length - from));
+            return tmp;
+        }
+
+        private static int GetLength(int from, int to)
+        {
+            int newLength = to - from;
+            if (newLength < 0)
+                throw new ArgumentException(from + " > " + to);
+            return newLength;
         }
 
         public static byte[] Append(byte[] a, byte b)
diff --git a/crypto/test/src/crypto/test/OCBTest.cs b/crypto/test/src/crypto/test/OCBTest.cs
index f2476f6d5..9f898fbe2 100644
--- a/crypto/test/src/crypto/test/OCBTest.cs
+++ b/crypto/test/src/crypto/test/OCBTest.cs
@@ -211,7 +211,7 @@ namespace Org.BouncyCastle.Crypto.Tests
         private void CheckTestCase(IAeadBlockCipher encCipher, IAeadBlockCipher decCipher, string testName,
             int macLengthBytes, byte[] P, byte[] C)
         {
-            byte[] tag = Arrays.Copy(C, C.Length - macLengthBytes, macLengthBytes);
+            byte[] tag = Arrays.CopyOfRange(C, C.Length - macLengthBytes, C.Length);
 
             {
                 byte[] enc = new byte[encCipher.GetOutputSize(P.Length)];