summary refs log tree commit diff
diff options
context:
space:
mode:
authorOren Novotny <oren@novotny.org>2014-10-14 08:22:06 -0400
committerOren Novotny <oren@novotny.org>2014-10-14 08:22:06 -0400
commit724177e7f792b0a2079082d47f5346b0b8fa55f1 (patch)
treeba4d5b5083ae34c23e5daa6d9e9e21d3db10cc54
parentskip slow tests (diff)
parentUse platform RNG as master, where available (diff)
downloadBouncyCastle.NET-ed25519-724177e7f792b0a2079082d47f5346b0b8fa55f1.tar.xz
Merge branch 'master' into master-vs12
-rw-r--r--crypto/crypto.csproj5
-rw-r--r--crypto/src/asn1/Asn1Set.cs100
-rw-r--r--crypto/src/crypto/agreement/DHStandardGroups.cs14
-rw-r--r--crypto/src/crypto/prng/CryptoApiRandomGenerator.cs94
-rw-r--r--crypto/src/crypto/signers/GenericSigner.cs229
-rw-r--r--crypto/src/crypto/tls/AbstractTlsServer.cs13
-rw-r--r--crypto/src/crypto/tls/AlertDescription.cs74
-rw-r--r--crypto/src/crypto/tls/AlertLevel.cs18
-rw-r--r--crypto/src/crypto/tls/ExporterLabel.cs5
-rw-r--r--crypto/src/crypto/tls/ExtensionType.cs18
-rw-r--r--crypto/src/crypto/tls/FiniteFieldDheGroup.cs21
-rw-r--r--crypto/src/crypto/tls/SecurityParameters.cs17
-rw-r--r--crypto/src/crypto/tls/TlsClientProtocol.cs58
-rw-r--r--crypto/src/crypto/tls/TlsDHUtilities.cs377
-rw-r--r--crypto/src/crypto/tls/TlsExtensionsUtilities.cs33
-rw-r--r--crypto/src/crypto/tls/TlsFatalAlert.cs2
-rw-r--r--crypto/src/crypto/tls/TlsServerProtocol.cs21
-rw-r--r--crypto/src/crypto/tls/TlsUtilities.cs21
-rw-r--r--crypto/src/openssl/PEMReader.cs2
-rw-r--r--crypto/src/pkcs/Pkcs12Store.cs465
-rw-r--r--crypto/src/security/PrivateKeyFactory.cs3
-rw-r--r--crypto/src/security/SecureRandom.cs439
-rw-r--r--crypto/test/src/crypto/test/OAEPTest.cs4
-rw-r--r--crypto/test/src/crypto/tls/test/MockTlsClient.cs10
-rw-r--r--crypto/test/src/crypto/tls/test/MockTlsServer.cs8
-rw-r--r--crypto/test/src/security/test/SecureRandomTest.cs291
26 files changed, 1404 insertions, 938 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index d65021a68..81f74e656 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4509,6 +4509,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\FiniteFieldDheGroup.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\HandshakeType.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/asn1/Asn1Set.cs b/crypto/src/asn1/Asn1Set.cs
index 2e77ca2a9..cf039d7fe 100644
--- a/crypto/src/asn1/Asn1Set.cs
+++ b/crypto/src/asn1/Asn1Set.cs
@@ -278,67 +278,30 @@ namespace Org.BouncyCastle.Asn1
             return encObj;
         }
 
-        /**
-         * return true if a &lt;= b (arrays are assumed padded with zeros).
-         */
-        private bool LessThanOrEqual(
-             byte[] a,
-             byte[] b)
+        protected internal void Sort()
         {
-            int len = System.Math.Min(a.Length, b.Length);
-            for (int i = 0; i != len; ++i)
+            if (_set.Count < 2)
+                return;
+
+            Asn1Encodable[] items = new Asn1Encodable[_set.Count];
+            byte[][] keys = new byte[_set.Count][];
+
+            for (int i = 0; i < _set.Count; ++i)
             {
-                if (a[i] != b[i])
-                {
-                    return a[i] < b[i];
-                }
+                Asn1Encodable item = (Asn1Encodable)_set[i];
+                items[i] = item;
+                keys[i] = item.GetEncoded(Asn1Encodable.Der);
             }
-            return len == a.Length;
-        }
 
-        protected internal void Sort()
-        {
-            if (_set.Count > 1)
-            {
-                bool swapped = true;
-                int lastSwap = _set.Count - 1;
+            Array.Sort(keys, items, new DerComparer());
 
-                while (swapped)
-                {
-                    int index = 0;
-                    int swapIndex = 0;
-                    byte[] a = ((Asn1Encodable) _set[0]).GetEncoded();
-
-                    swapped = false;
-
-                    while (index != lastSwap)
-                    {
-                        byte[] b = ((Asn1Encodable) _set[index + 1]).GetEncoded();
-
-                        if (LessThanOrEqual(a, b))
-                        {
-                            a = b;
-                        }
-                        else
-                        {
-                            object o = _set[index];
-                            _set[index] = _set[index + 1];
-                            _set[index + 1] = o;
-
-                            swapped = true;
-                            swapIndex = index;
-                        }
-
-                        index++;
-                    }
-
-                    lastSwap = swapIndex;
-                }
+            for (int i = 0; i < _set.Count; ++i)
+            {
+                _set[i] = items[i];
             }
         }
 
-        protected internal void AddObject(
-            Asn1Encodable obj)
+        protected internal void AddObject(Asn1Encodable obj)
         {
             _set.Add(obj);
         }
@@ -347,5 +310,36 @@ namespace Org.BouncyCastle.Asn1
         {
             return CollectionUtilities.ToString(_set);
         }
+
+        private class DerComparer
+            :   IComparer
+        {
+            public int Compare(object x, object y)
+            {
+                byte[] a = (byte[])x, b = (byte[])y;
+                int len = System.Math.Min(a.Length, b.Length);
+                for (int i = 0; i != len; ++i)
+                {
+                    byte ai = a[i], bi = b[i];
+                    if (ai != bi)
+                        return ai < bi ? -1 : 1;
+                }
+                if (a.Length > b.Length)
+                    return AllZeroesFrom(a, len) ? 0 : 1;
+                if (a.Length < b.Length)
+                    return AllZeroesFrom(b, len) ? 0 : -1;
+                return 0;
+            }
+
+            private bool AllZeroesFrom(byte[] bs, int pos)
+            {
+                while (pos < bs.Length)
+                {
+                    if (bs[pos++] != 0)
+                        return false;
+                }
+                return true;
+            }
+        }
     }
 }
diff --git a/crypto/src/crypto/agreement/DHStandardGroups.cs b/crypto/src/crypto/agreement/DHStandardGroups.cs
index 6c46b60de..93b65af98 100644
--- a/crypto/src/crypto/agreement/DHStandardGroups.cs
+++ b/crypto/src/crypto/agreement/DHStandardGroups.cs
@@ -9,19 +9,19 @@ namespace Org.BouncyCastle.Crypto.Agreement
     /// <summary>Standard Diffie-Hellman groups from various IETF specifications.</summary>
     public class DHStandardGroups
     {
+        private static BigInteger FromHex(string hex)
+        {
+            return new BigInteger(1, Hex.Decode(hex));
+        }
+
         private static DHParameters FromPG(string hexP, string hexG)
         {
-            BigInteger p = new BigInteger(1, Hex.Decode(hexP));
-            BigInteger g = new BigInteger(1, Hex.Decode(hexG));
-            return new DHParameters(p, g);
+            return new DHParameters(FromHex(hexP), FromHex(hexG));
         }
 
         private static DHParameters FromPGQ(string hexP, string hexG, string hexQ)
         {
-            BigInteger p = new BigInteger(1, Hex.Decode(hexP));
-            BigInteger g = new BigInteger(1, Hex.Decode(hexG));
-            BigInteger q = new BigInteger(1, Hex.Decode(hexQ));
-            return new DHParameters(p, g, q);
+            return new DHParameters(FromHex(hexP), FromHex(hexG), FromHex(hexQ));
         }
 
         /*
diff --git a/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs b/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
index e1a0d4012..521fae33e 100644
--- a/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
+++ b/crypto/src/crypto/prng/CryptoApiRandomGenerator.cs
@@ -5,62 +5,62 @@ using System.Security.Cryptography;
 
 namespace Org.BouncyCastle.Crypto.Prng
 {
-	/// <summary>
-	/// Uses Microsoft's RNGCryptoServiceProvider
-	/// </summary>
-	public class CryptoApiRandomGenerator
-		: IRandomGenerator
-	{
-		private readonly RandomNumberGenerator rndProv;
+    /// <summary>
+    /// Uses Microsoft's RNGCryptoServiceProvider
+    /// </summary>
+    public class CryptoApiRandomGenerator
+        : IRandomGenerator
+    {
+        private readonly RandomNumberGenerator rndProv;
 
-		public CryptoApiRandomGenerator()
-			: this(new RNGCryptoServiceProvider())
-		{
-		}
+        public CryptoApiRandomGenerator()
+            : this(new RNGCryptoServiceProvider())
+        {
+        }
 
-		public CryptoApiRandomGenerator(RandomNumberGenerator rng)
-		{
-			this.rndProv = rng;
-		}
+        public CryptoApiRandomGenerator(RandomNumberGenerator rng)
+        {
+            this.rndProv = rng;
+        }
 
-		#region IRandomGenerator Members
+        #region IRandomGenerator Members
 
-		public virtual void AddSeedMaterial(byte[] seed)
-		{
-			// We don't care about the seed
-		}
+        public virtual void AddSeedMaterial(byte[] seed)
+        {
+            // We don't care about the seed
+        }
 
-		public virtual void AddSeedMaterial(long seed)
-		{
-			// We don't care about the seed
-		}
+        public virtual void AddSeedMaterial(long seed)
+        {
+            // We don't care about the seed
+        }
 
-		public virtual void NextBytes(byte[] bytes)
-		{
-			rndProv.GetBytes(bytes);
-		}
+        public virtual void NextBytes(byte[] bytes)
+        {
+            rndProv.GetBytes(bytes);
+        }
 
-		public virtual void NextBytes(byte[] bytes, int start, int len)
-		{
-			if (start < 0)
-				throw new ArgumentException("Start offset cannot be negative", "start");
-			if (bytes.Length < (start + len))
-				throw new ArgumentException("Byte array too small for requested offset and length");
+        public virtual void NextBytes(byte[] bytes, int start, int len)
+        {
+            if (start < 0)
+                throw new ArgumentException("Start offset cannot be negative", "start");
+            if (bytes.Length < (start + len))
+                throw new ArgumentException("Byte array too small for requested offset and length");
 
-			if (bytes.Length == len && start == 0) 
-			{
-				NextBytes(bytes);
-			}
-			else 
-			{
-				byte[] tmpBuf = new byte[len];
-				rndProv.GetBytes(tmpBuf);
-				Array.Copy(tmpBuf, 0, bytes, start, len);
-			}
-		}
+            if (bytes.Length == len && start == 0) 
+            {
+                NextBytes(bytes);
+            }
+            else 
+            {
+                byte[] tmpBuf = new byte[len];
+                NextBytes(tmpBuf);
+                Array.Copy(tmpBuf, 0, bytes, start, len);
+            }
+        }
 
-		#endregion
-	}
+        #endregion
+    }
 }
 
 #endif
diff --git a/crypto/src/crypto/signers/GenericSigner.cs b/crypto/src/crypto/signers/GenericSigner.cs
index 1a53eee2b..5035b454d 100644
--- a/crypto/src/crypto/signers/GenericSigner.cs
+++ b/crypto/src/crypto/signers/GenericSigner.cs
@@ -6,124 +6,125 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Signers
 {
-	public class GenericSigner
-		: ISigner
-	{
-		private readonly IAsymmetricBlockCipher engine;
-		private readonly IDigest digest;
-		private bool forSigning;
-
-		public GenericSigner(
-			IAsymmetricBlockCipher	engine,
-			IDigest					digest)
-		{
-			this.engine = engine;
-			this.digest = digest;
-		}
-
-		public string AlgorithmName
-		{
-			get { return "Generic(" + engine.AlgorithmName + "/" + digest.AlgorithmName + ")"; }
-		}
-
-		/**
-		* initialise the signer for signing or verification.
-		*
-		* @param forSigning
-		*            true if for signing, false otherwise
-		* @param parameters
-		*            necessary parameters.
-		*/
-		public void Init(
-			bool				forSigning,
-			ICipherParameters	parameters)
-		{
-			this.forSigning = forSigning;
-			AsymmetricKeyParameter k;
-
-			if (parameters is ParametersWithRandom)
-			{
-				k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters;
-			}
-			else
-			{
-				k = (AsymmetricKeyParameter)parameters;
-			}
+    public class GenericSigner
+        : ISigner
+    {
+        private readonly IAsymmetricBlockCipher engine;
+        private readonly IDigest digest;
+        private bool forSigning;
+
+        public GenericSigner(
+            IAsymmetricBlockCipher	engine,
+            IDigest					digest)
+        {
+            this.engine = engine;
+            this.digest = digest;
+        }
+
+        public string AlgorithmName
+        {
+            get { return "Generic(" + engine.AlgorithmName + "/" + digest.AlgorithmName + ")"; }
+        }
+
+        /**
+        * initialise the signer for signing or verification.
+        *
+        * @param forSigning
+        *            true if for signing, false otherwise
+        * @param parameters
+        *            necessary parameters.
+        */
+        public void Init(bool forSigning, ICipherParameters parameters)
+        {
+            this.forSigning = forSigning;
+
+            AsymmetricKeyParameter k;
+            if (parameters is ParametersWithRandom)
+            {
+                k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters;
+            }
+            else
+            {
+                k = (AsymmetricKeyParameter)parameters;
+            }
 
             if (forSigning && !k.IsPrivate)
                 throw new InvalidKeyException("Signing requires private key.");
 
-			if (!forSigning && k.IsPrivate)
+            if (!forSigning && k.IsPrivate)
                 throw new InvalidKeyException("Verification requires public key.");
 
-			Reset();
-
-			engine.Init(forSigning, parameters);
-		}
-
-		/**
-		* update the internal digest with the byte b
-		*/
-		public void Update(
-			byte input)
-		{
-			digest.Update(input);
-		}
-
-		/**
-		* update the internal digest with the byte array in
-		*/
-		public void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		length)
-		{
-			digest.BlockUpdate(input, inOff, length);
-		}
-
-		/**
-		* Generate a signature for the message we've been loaded with using the key
-		* we were initialised with.
-		*/
-		public byte[] GenerateSignature()
-		{
-			if (!forSigning)
-				throw new InvalidOperationException("GenericSigner not initialised for signature generation.");
-
-			byte[] hash = new byte[digest.GetDigestSize()];
-			digest.DoFinal(hash, 0);
-
-			return engine.ProcessBlock(hash, 0, hash.Length);
-		}
-
-		/**
-		* return true if the internal state represents the signature described in
-		* the passed in array.
-		*/
-		public bool VerifySignature(
-			byte[] signature)
-		{
-			if (forSigning)
-				throw new InvalidOperationException("GenericSigner not initialised for verification");
-
-			byte[] hash = new byte[digest.GetDigestSize()];
-			digest.DoFinal(hash, 0);
-
-			try
-			{
-				byte[] sig = engine.ProcessBlock(signature, 0, signature.Length);
-
-				return Arrays.ConstantTimeAreEqual(sig, hash);
-			}
-			catch (Exception)
-			{
-				return false;
-			}
-		}
-
-		public void Reset()
-		{
-			digest.Reset();
-		}
-	}
+            Reset();
+
+            engine.Init(forSigning, parameters);
+        }
+
+        /**
+        * update the internal digest with the byte b
+        */
+        public void Update(byte input)
+        {
+            digest.Update(input);
+        }
+
+        /**
+        * update the internal digest with the byte array in
+        */
+        public void BlockUpdate(byte[] input, int inOff, int length)
+        {
+            digest.BlockUpdate(input, inOff, length);
+        }
+
+        /**
+        * Generate a signature for the message we've been loaded with using the key
+        * we were initialised with.
+        */
+        public byte[] GenerateSignature()
+        {
+            if (!forSigning)
+                throw new InvalidOperationException("GenericSigner not initialised for signature generation.");
+
+            byte[] hash = new byte[digest.GetDigestSize()];
+            digest.DoFinal(hash, 0);
+
+            return engine.ProcessBlock(hash, 0, hash.Length);
+        }
+
+        /**
+        * return true if the internal state represents the signature described in
+        * the passed in array.
+        */
+        public bool VerifySignature(byte[] signature)
+        {
+            if (forSigning)
+                throw new InvalidOperationException("GenericSigner not initialised for verification");
+
+            byte[] hash = new byte[digest.GetDigestSize()];
+            digest.DoFinal(hash, 0);
+
+            try
+            {
+                byte[] sig = engine.ProcessBlock(signature, 0, signature.Length);
+
+                // Extend with leading zeroes to match the digest size, if necessary.
+                if (sig.Length < hash.Length)
+                {
+                    byte[] tmp = new byte[hash.Length];
+                    Array.Copy(sig, 0, tmp, tmp.Length - sig.Length, sig.Length);
+                    sig = tmp;
+                }
+
+                return Arrays.ConstantTimeAreEqual(sig, hash);
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        public void Reset()
+        {
+            digest.Reset();
+        }
+    }
 }
diff --git a/crypto/src/crypto/tls/AbstractTlsServer.cs b/crypto/src/crypto/tls/AbstractTlsServer.cs
index 47542c796..c2c6fd57c 100644
--- a/crypto/src/crypto/tls/AbstractTlsServer.cs
+++ b/crypto/src/crypto/tls/AbstractTlsServer.cs
@@ -223,9 +223,10 @@ namespace Org.BouncyCastle.Crypto.Tls
             if (this.mEncryptThenMacOffered && AllowEncryptThenMac)
             {
                 /*
-                 * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
-                 * request extension from a client and then selects a stream or AEAD cipher suite, it
-                 * MUST NOT send an encrypt-then-MAC response extension back to the client.
+                 * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                 * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                 * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                 * client.
                  */
                 if (TlsUtilities.IsBlockCipherSuite(this.mSelectedCipherSuite))
                 {
@@ -233,9 +234,11 @@ namespace Org.BouncyCastle.Crypto.Tls
                 }
             }
 
-            if (this.mMaxFragmentLengthOffered >= 0)
+            if (this.mMaxFragmentLengthOffered >= 0
+                && TlsUtilities.IsValidUint8(mMaxFragmentLengthOffered)
+                && MaxFragmentLength.IsValid((byte)mMaxFragmentLengthOffered))
             {
-                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(CheckServerExtensions(), (byte)this.mMaxFragmentLengthOffered);
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(CheckServerExtensions(), (byte)mMaxFragmentLengthOffered);
             }
 
             if (this.mTruncatedHMacOffered && AllowTruncatedHMac)
diff --git a/crypto/src/crypto/tls/AlertDescription.cs b/crypto/src/crypto/tls/AlertDescription.cs
index e09da6cab..5b6e88bf7 100644
--- a/crypto/src/crypto/tls/AlertDescription.cs
+++ b/crypto/src/crypto/tls/AlertDescription.cs
@@ -213,5 +213,79 @@ namespace Org.BouncyCastle.Crypto.Tls
          * "unknown_psk_identity" alert message.
          */
         public const byte unknown_psk_identity = 115;
+
+        public static string GetName(byte alertDescription)
+        {
+            switch (alertDescription)
+            {
+            case close_notify:
+                return "close_notify";
+            case unexpected_message:
+                return "unexpected_message";
+            case bad_record_mac:
+                return "bad_record_mac";
+            case decryption_failed:
+                return "decryption_failed";
+            case record_overflow:
+                return "record_overflow";
+            case decompression_failure:
+                return "decompression_failure";
+            case handshake_failure:
+                return "handshake_failure";
+            case no_certificate:
+                return "no_certificate";
+            case bad_certificate:
+                return "bad_certificate";
+            case unsupported_certificate:
+                return "unsupported_certificate";
+            case certificate_revoked:
+                return "certificate_revoked";
+            case certificate_expired:
+                return "certificate_expired";
+            case certificate_unknown:
+                return "certificate_unknown";
+            case illegal_parameter:
+                return "illegal_parameter";
+            case unknown_ca:
+                return "unknown_ca";
+            case access_denied:
+                return "access_denied";
+            case decode_error:
+                return "decode_error";
+            case decrypt_error:
+                return "decrypt_error";
+            case export_restriction:
+                return "export_restriction";
+            case protocol_version:
+                return "protocol_version";
+            case insufficient_security:
+                return "insufficient_security";
+            case internal_error:
+                return "internal_error";
+            case user_canceled:
+                return "user_canceled";
+            case no_renegotiation:
+                return "no_renegotiation";
+            case unsupported_extension:
+                return "unsupported_extension";
+            case certificate_unobtainable:
+                return "certificate_unobtainable";
+            case unrecognized_name:
+                return "unrecognized_name";
+            case bad_certificate_status_response:
+                return "bad_certificate_status_response";
+            case bad_certificate_hash_value:
+                return "bad_certificate_hash_value";
+            case unknown_psk_identity:
+                return "unknown_psk_identity";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(byte alertDescription)
+        {
+            return GetName(alertDescription) + "(" + alertDescription + ")";
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/AlertLevel.cs b/crypto/src/crypto/tls/AlertLevel.cs
index d77251dfb..9461a0b58 100644
--- a/crypto/src/crypto/tls/AlertLevel.cs
+++ b/crypto/src/crypto/tls/AlertLevel.cs
@@ -7,5 +7,23 @@ namespace Org.BouncyCastle.Crypto.Tls
     {
         public const byte warning = 1;
         public const byte fatal = 2;
+
+        public static string GetName(byte alertDescription)
+        {
+            switch (alertDescription)
+            {
+            case warning:
+                return "warning";
+            case fatal:
+                return "fatal";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(byte alertDescription)
+        {
+            return GetName(alertDescription) + "(" + alertDescription + ")";
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/ExporterLabel.cs b/crypto/src/crypto/tls/ExporterLabel.cs
index f301ea3c0..280321e2a 100644
--- a/crypto/src/crypto/tls/ExporterLabel.cs
+++ b/crypto/src/crypto/tls/ExporterLabel.cs
@@ -28,5 +28,10 @@ namespace Org.BouncyCastle.Crypto.Tls
          * RFC 5764
          */
         public const string dtls_srtp = "EXTRACTOR-dtls_srtp";
+
+        /*
+         * draft-ietf-tls-session-hash-01
+         */
+        public static readonly string extended_master_secret = "extended master secret";
     }
 }
diff --git a/crypto/src/crypto/tls/ExtensionType.cs b/crypto/src/crypto/tls/ExtensionType.cs
index 929c134d5..acee380b6 100644
--- a/crypto/src/crypto/tls/ExtensionType.cs
+++ b/crypto/src/crypto/tls/ExtensionType.cs
@@ -44,14 +44,28 @@ namespace Org.BouncyCastle.Crypto.Tls
         public const int heartbeat = 15;
 
         /*
+         * RFC 7366
+         */
+        public const int encrypt_then_mac = 22;
+
+        /*
+         * draft-ietf-tls-session-hash-01
+         * 
+         * NOTE: Early code-point assignment
+         */
+        public const int extended_master_secret = 23;
+
+        /*
          * RFC 5077 7.
          */
         public const int session_ticket = 35;
 
         /*
-         * draft-ietf-tls-encrypt-then-mac-03
+         * draft-ietf-tls-negotiated-ff-dhe-01
+         * 
+         * WARNING: Placeholder value; the real value is TBA
          */
-        public const int encrypt_then_mac = 22;
+        public static readonly int negotiated_ff_dhe_groups = 101;
 
         /*
          * RFC 5746 3.2.
diff --git a/crypto/src/crypto/tls/FiniteFieldDheGroup.cs b/crypto/src/crypto/tls/FiniteFieldDheGroup.cs
new file mode 100644
index 000000000..437504941
--- /dev/null
+++ b/crypto/src/crypto/tls/FiniteFieldDheGroup.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    /*
+     * draft-ietf-tls-negotiated-ff-dhe-01
+     */
+    public abstract class FiniteFieldDheGroup
+    {
+        public const byte ffdhe2432 = 0;
+        public const byte ffdhe3072 = 1;
+        public const byte ffdhe4096 = 2;
+        public const byte ffdhe6144 = 3;
+        public const byte ffdhe8192 = 4;
+
+        public static bool IsValid(byte group)
+        {
+            return group >= ffdhe2432 && group <= ffdhe8192;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/SecurityParameters.cs b/crypto/src/crypto/tls/SecurityParameters.cs
index 6a115a911..12bb59f22 100644
--- a/crypto/src/crypto/tls/SecurityParameters.cs
+++ b/crypto/src/crypto/tls/SecurityParameters.cs
@@ -14,21 +14,13 @@ namespace Org.BouncyCastle.Crypto.Tls
         internal byte[] masterSecret = null;
         internal byte[] clientRandom = null;
         internal byte[] serverRandom = null;
+        internal byte[] sessionHash = 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.prfAlgorithm = other.prfAlgorithm;
-            this.verifyDataLength = other.verifyDataLength;
-            this.masterSecret = Arrays.Clone(other.masterSecret);
-        }
+        internal bool extendedMasterSecret = false;
 
         internal virtual void Clear()
         {
@@ -90,5 +82,10 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             get { return serverRandom; }
         }
+
+        public virtual byte[] SessionHash
+        {
+            get { return sessionHash; }
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs
index e48c92d30..9fe50add8 100644
--- a/crypto/src/crypto/tls/TlsClientProtocol.cs
+++ b/crypto/src/crypto/tls/TlsClientProtocol.cs
@@ -360,11 +360,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                     SendClientKeyExchangeMessage();
                     this.mConnectionState = CS_CLIENT_KEY_EXCHANGE;
 
+                    TlsHandshakeHash prepareFinishHash = mRecordStream.PrepareToFinish();
+                    this.mSecurityParameters.sessionHash = GetCurrentPrfHash(Context, prepareFinishHash, null);
+
                     EstablishMasterSecret(Context, mKeyExchange);
                     mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher());
 
-                    TlsHandshakeHash prepareFinishHash = mRecordStream.PrepareToFinish();
-
                     if (clientCreds != null && clientCreds is TlsSignerCredentials)
                     {
                         TlsSignerCredentials signerCredentials = (TlsSignerCredentials)clientCreds;
@@ -386,7 +387,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                         else
                         {
                             signatureAndHashAlgorithm = null;
-                            hash = GetCurrentPrfHash(Context, prepareFinishHash, null);
+                            hash = mSecurityParameters.SessionHash;
                         }
 
                         byte[] signature = signerCredentials.GenerateCertificateSignature(hash);
@@ -562,7 +563,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf);
 
-            TlsProtocol.AssertEmpty(buf);
+            AssertEmpty(buf);
 
             mTlsClient.NotifyNewSessionTicket(newSessionTicket);
         }
@@ -635,6 +636,15 @@ namespace Org.BouncyCastle.Crypto.Tls
             this.mServerExtensions = ReadExtensions(buf);
 
             /*
+             * draft-ietf-tls-session-hash-01 5.2. If a server receives the "extended_master_secret"
+             * extension, it MUST include the "extended_master_secret" extension in its ServerHello
+             * message.
+             */
+            bool serverSentExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(mServerExtensions);
+            if (serverSentExtendedMasterSecret != mSecurityParameters.extendedMasterSecret)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            /*
              * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
              * extended client hello message.
              * 
@@ -656,6 +666,25 @@ namespace Org.BouncyCastle.Crypto.Tls
                         continue;
 
                     /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                     * same extension type appeared in the corresponding ClientHello. If a client
+                     * receives an extension type in ServerHello that it did not request in the
+                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                     * fatal alert.
+                     */
+                    if (null == TlsUtilities.GetExtensionData(this.mClientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+
+                    /*
+                     * draft-ietf-tls-session-hash-01 5.2. Implementation note: if the server decides to
+                     * proceed with resumption, the extension does not have any effect. Requiring the
+                     * extension to be included anyway makes the extension negotiation logic easier,
+                     * because it does not depend on whether resumption is accepted or not.
+                     */
+                    if (extType == ExtensionType.extended_master_secret)
+                        continue;
+
+                    /*
                      * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
                      * extensions appearing in the client hello, and Send a server hello containing no
                      * extensions[.]
@@ -667,16 +696,6 @@ namespace Org.BouncyCastle.Crypto.Tls
                         // TODO[compat-polarssl] PolarSSL test server Sends server extensions e.g. ec_point_formats
     //                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
                     }
-
-                    /*
-                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
-                     * same extension type appeared in the corresponding ClientHello. If a client
-                     * receives an extension type in ServerHello that it did not request in the
-                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
-                     * fatal alert.
-                     */
-                    if (null == TlsUtilities.GetExtensionData(this.mClientExtensions, extType))
-                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
                 }
             }
 
@@ -718,6 +737,8 @@ namespace Org.BouncyCastle.Crypto.Tls
 
                 sessionClientExtensions = null;
                 sessionServerExtensions = this.mSessionParameters.ReadServerExtensions();
+
+                this.mSecurityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(sessionServerExtensions);
             }
 
             this.mSecurityParameters.cipherSuite = selectedCipherSuite;
@@ -726,9 +747,10 @@ namespace Org.BouncyCastle.Crypto.Tls
             if (sessionServerExtensions != null)
             {
                 /*
-                 * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
-                 * request extension from a client and then selects a stream or AEAD cipher suite, it
-                 * MUST NOT Send an encrypt-then-MAC response extension back to the client.
+                 * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                 * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                 * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                 * client.
                  */
                 bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(sessionServerExtensions);
                 if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(selectedCipherSuite))
@@ -808,6 +830,8 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             this.mClientExtensions = this.mTlsClient.GetClientExtensions();
 
+            this.mSecurityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(mClientExtensions);
+
             HandshakeMessage message = new HandshakeMessage(HandshakeType.client_hello);
 
             TlsUtilities.WriteVersion(client_version, message);
diff --git a/crypto/src/crypto/tls/TlsDHUtilities.cs b/crypto/src/crypto/tls/TlsDHUtilities.cs
index 477c3ebac..b29f75e30 100644
--- a/crypto/src/crypto/tls/TlsDHUtilities.cs
+++ b/crypto/src/crypto/tls/TlsDHUtilities.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections;
 using System.IO;
 
 using Org.BouncyCastle.Crypto.Agreement;
@@ -7,14 +8,386 @@ using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
     public abstract class TlsDHUtilities
     {
-        internal static readonly BigInteger One = BigInteger.One;
         internal static readonly BigInteger Two = BigInteger.Two;
 
+        /*
+         * TODO[draft-ietf-tls-negotiated-ff-dhe-01] Move these groups to DHStandardGroups once reaches RFC
+         */
+        private static BigInteger FromHex(String hex)
+        {
+            return new BigInteger(1, Hex.Decode(hex));
+        }
+
+        private static DHParameters FromSafeP(String hexP)
+        {
+            BigInteger p = FromHex(hexP), q = p.ShiftRight(1);
+            return new DHParameters(p, Two, q);
+        }
+
+        private static readonly string draft_ffdhe2432_p =
+              "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9"
+            + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935"
+            + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB"
+            + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"
+            + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA"
+            + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C"
+            + "AEFE13098533C8B3FFFFFFFFFFFFFFFF";
+        internal static readonly DHParameters draft_ffdhe2432 = FromSafeP(draft_ffdhe2432_p);
+
+        private static readonly string draft_ffdhe3072_p =
+              "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9"
+            + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935"
+            + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB"
+            + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"
+            + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA"
+            + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C"
+            + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D"
+            + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF";
+        internal static readonly DHParameters draft_ffdhe3072 = FromSafeP(draft_ffdhe3072_p);
+
+        private static readonly string draft_ffdhe4096_p =
+              "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9"
+            + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935"
+            + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB"
+            + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"
+            + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA"
+            + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C"
+            + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D"
+            + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB"
+            + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832"
+            + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF"
+            + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A"
+            + "FFFFFFFFFFFFFFFF";
+        internal static readonly DHParameters draft_ffdhe4096 = FromSafeP(draft_ffdhe4096_p);
+
+        private static readonly string draft_ffdhe6144_p =
+              "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9"
+            + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935"
+            + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB"
+            + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"
+            + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA"
+            + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C"
+            + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D"
+            + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB"
+            + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832"
+            + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF"
+            + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902"
+            + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6"
+            + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A"
+            + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477"
+            + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3"
+            + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4"
+            + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6"
+            + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C"
+            + "D72B03746AE77F5E62292C311562A846505DC82DB854338A"
+            + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04"
+            + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1"
+            + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF";
+        internal static readonly DHParameters draft_ffdhe6144 = FromSafeP(draft_ffdhe6144_p);
+
+        private static readonly string draft_ffdhe8192_p =
+              "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9"
+            + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935"
+            + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB"
+            + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"
+            + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA"
+            + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C"
+            + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D"
+            + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB"
+            + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832"
+            + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF"
+            + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902"
+            + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6"
+            + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A"
+            + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477"
+            + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3"
+            + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4"
+            + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6"
+            + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C"
+            + "D72B03746AE77F5E62292C311562A846505DC82DB854338A"
+            + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04"
+            + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1"
+            + "A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838"
+            + "1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E"
+            + "0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665"
+            + "CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282"
+            + "2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022"
+            + "BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C"
+            + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9"
+            + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457"
+            + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30"
+            + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D"
+            + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C"
+            + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF";
+        internal static readonly DHParameters draft_ffdhe8192 = FromSafeP(draft_ffdhe8192_p);
+
+    
+        public static void AddNegotiatedDheGroupsClientExtension(IDictionary extensions, byte[] dheGroups)
+        {
+            extensions[ExtensionType.negotiated_ff_dhe_groups] = CreateNegotiatedDheGroupsClientExtension(dheGroups);
+        }
+
+        public static void AddNegotiatedDheGroupsServerExtension(IDictionary extensions, byte dheGroup)
+        {
+            extensions[ExtensionType.negotiated_ff_dhe_groups] = CreateNegotiatedDheGroupsServerExtension(dheGroup);
+        }
+
+        public static byte[] GetNegotiatedDheGroupsClientExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.negotiated_ff_dhe_groups);
+            return extensionData == null ? null : ReadNegotiatedDheGroupsClientExtension(extensionData);
+        }
+
+        public static short GetNegotiatedDheGroupsServerExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.negotiated_ff_dhe_groups);
+            return extensionData == null ? (short)-1 : (short)ReadNegotiatedDheGroupsServerExtension(extensionData);
+        }
+
+        public static byte[] CreateNegotiatedDheGroupsClientExtension(byte[] dheGroups)
+        {
+            if (dheGroups == null || dheGroups.Length < 1 || dheGroups.Length > 255)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeUint8ArrayWithUint8Length(dheGroups);
+        }
+
+        public static byte[] CreateNegotiatedDheGroupsServerExtension(byte dheGroup)
+        {
+            return new byte[]{ dheGroup };
+        }
+
+        public static byte[] ReadNegotiatedDheGroupsClientExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            byte length = TlsUtilities.ReadUint8(buf);
+            if (length < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            byte[] dheGroups = TlsUtilities.ReadUint8Array(length, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return dheGroups;
+        }
+
+        public static byte ReadNegotiatedDheGroupsServerExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            if (extensionData.Length != 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return extensionData[0];
+        }
+
+        public static DHParameters GetParametersForDHEGroup(short dheGroup)
+        {
+            switch (dheGroup)
+            {
+            case FiniteFieldDheGroup.ffdhe2432:
+                return draft_ffdhe2432;
+            case FiniteFieldDheGroup.ffdhe3072:
+                return draft_ffdhe3072;
+            case FiniteFieldDheGroup.ffdhe4096:
+                return draft_ffdhe4096;
+            case FiniteFieldDheGroup.ffdhe6144:
+                return draft_ffdhe6144;
+            case FiniteFieldDheGroup.ffdhe8192:
+                return draft_ffdhe8192;
+            default:
+                return null;
+            }
+        }
+
+        public static bool ContainsDheCipherSuites(int[] cipherSuites)
+        {
+            for (int i = 0; i < cipherSuites.Length; ++i)
+            {
+                if (IsDheCipherSuite(cipherSuites[i]))
+                    return true;
+            }
+            return false;
+        }
+
+        public static bool IsDheCipherSuite(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            /*
+             * RFC 2246
+             */
+            case CipherSuite.TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_DES_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+
+            /*
+             * RFC 3268
+             */
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+
+            /*
+             * RFC 5932
+             */
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+
+            /*
+             * RFC 4162
+             */
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+
+            /*
+             * RFC 4279
+             */
+            case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+
+            /*
+             * RFC 4785
+             */
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+
+            /*
+             * RFC 5246
+             */
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+
+            /*
+             * RFC 5288
+             */
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+
+            /*
+             * RFC 5487
+             */
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+
+            /*
+             * RFC 6367
+             */
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+
+            /*
+             * RFC 6655
+             */
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+
+            /*
+             * draft-agl-tls-chacha20poly1305-04
+             */
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+
+            /*
+             * draft-josefsson-salsa20-tls-04
+             */
+            case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+
+                return true;
+
+            default:
+                return false;
+            }
+        }
+
         public static bool AreCompatibleParameters(DHParameters a, DHParameters b)
         {
             return a.P.Equals(b.P) && a.G.Equals(b.G);
@@ -78,7 +451,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             {
                 throw new TlsFatalAlert(AlertDescription.illegal_parameter);
             }
-            if (Y.CompareTo(Two) < 0 || Y.CompareTo(p.Subtract(One)) > 0)
+            if (Y.CompareTo(Two) < 0 || Y.CompareTo(p.Subtract(Two)) > 0)
             {
                 throw new TlsFatalAlert(AlertDescription.illegal_parameter);
             }
diff --git a/crypto/src/crypto/tls/TlsExtensionsUtilities.cs b/crypto/src/crypto/tls/TlsExtensionsUtilities.cs
index 8876911e6..46851b66c 100644
--- a/crypto/src/crypto/tls/TlsExtensionsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsExtensionsUtilities.cs
@@ -18,6 +18,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             extensions[ExtensionType.encrypt_then_mac] = CreateEncryptThenMacExtension();
         }
 
+        public static void AddExtendedMasterSecretExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.extended_master_secret] = CreateExtendedMasterSecretExtension();
+        }
+
         /// <exception cref="IOException"></exception>
         public static void AddHeartbeatExtension(IDictionary extensions, HeartbeatExtension heartbeatExtension)
         {
@@ -83,6 +88,13 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /// <exception cref="IOException"></exception>
+        public static bool HasExtendedMasterSecretExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.extended_master_secret);
+            return extensionData == null ? false : ReadExtendedMasterSecretExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"></exception>
         public static bool HasTruncatedHMacExtension(IDictionary extensions)
         {
             byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.truncated_hmac);
@@ -99,6 +111,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             return CreateEmptyExtensionData();
         }
 
+        public static byte[] CreateExtendedMasterSecretExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
         /// <exception cref="IOException"></exception>
         public static byte[] CreateHeartbeatExtension(HeartbeatExtension heartbeatExtension)
         {
@@ -115,9 +132,6 @@ namespace Org.BouncyCastle.Crypto.Tls
         /// <exception cref="IOException"></exception>
         public static byte[] CreateMaxFragmentLengthExtension(byte maxFragmentLength)
         {
-            if (!MaxFragmentLength.IsValid(maxFragmentLength))
-                throw new TlsFatalAlert(AlertDescription.internal_error);
-
             return new byte[]{ maxFragmentLength };
         }
 
@@ -173,6 +187,12 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /// <exception cref="IOException"></exception>
+        public static bool ReadExtendedMasterSecretExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"></exception>
         public static HeartbeatExtension ReadHeartbeatExtension(byte[] extensionData)
         {
             if (extensionData == null)
@@ -196,12 +216,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             if (extensionData.Length != 1)
                 throw new TlsFatalAlert(AlertDescription.decode_error);
 
-            byte maxFragmentLength = extensionData[0];
-
-            if (!MaxFragmentLength.IsValid(maxFragmentLength))
-                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
-
-            return maxFragmentLength;
+            return extensionData[0];
         }
 
         /// <exception cref="IOException"></exception>
diff --git a/crypto/src/crypto/tls/TlsFatalAlert.cs b/crypto/src/crypto/tls/TlsFatalAlert.cs
index 0c7ed88d9..55d784dd9 100644
--- a/crypto/src/crypto/tls/TlsFatalAlert.cs
+++ b/crypto/src/crypto/tls/TlsFatalAlert.cs
@@ -14,7 +14,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         public TlsFatalAlert(byte alertDescription, Exception alertCause)
-            :   base("Fatal alert: " + alertDescription, alertCause)
+            : base(Tls.AlertDescription.GetText(alertDescription), alertCause)
         {
             this.alertDescription = alertDescription;
         }
diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs
index 589ede802..165d6a147 100644
--- a/crypto/src/crypto/tls/TlsServerProtocol.cs
+++ b/crypto/src/crypto/tls/TlsServerProtocol.cs
@@ -422,14 +422,14 @@ namespace Org.BouncyCastle.Crypto.Tls
             // Verify the CertificateVerify message contains a correct signature.
             try
             {
-                byte[] certificateVerifyHash;
+                byte[] hash;
                 if (TlsUtilities.IsTlsV12(Context))
                 {
-                    certificateVerifyHash = mPrepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash);
+                    hash = mPrepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash);
                 }
                 else
                 {
-                    certificateVerifyHash = TlsProtocol.GetCurrentPrfHash(Context, mPrepareFinishHash, null);
+                    hash = mSecurityParameters.SessionHash;
                 }
 
                 X509CertificateStructure x509Cert = mPeerCertificate.GetCertificateAt(0);
@@ -439,7 +439,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                 TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)mClientCertificateType);
                 tlsSigner.Init(Context);
                 if (!tlsSigner.VerifyRawSignature(clientCertificateVerify.Algorithm,
-                    clientCertificateVerify.Signature, publicKey, certificateVerifyHash))
+                    clientCertificateVerify.Signature, publicKey, hash))
                 {
                     throw new TlsFatalAlert(AlertDescription.decrypt_error);
                 }
@@ -494,6 +494,8 @@ namespace Org.BouncyCastle.Crypto.Tls
              */
             this.mClientExtensions = ReadExtensions(buf);
 
+            this.mSecurityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(mClientExtensions);
+
             ContextAdmin.SetClientVersion(client_version);
 
             mTlsServer.NotifyClientVersion(client_version);
@@ -556,11 +558,12 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             AssertEmpty(buf);
 
+            this.mPrepareFinishHash = mRecordStream.PrepareToFinish();
+            this.mSecurityParameters.sessionHash = GetCurrentPrfHash(Context, mPrepareFinishHash, null);
+
             EstablishMasterSecret(Context, mKeyExchange);
             mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher());
 
-            this.mPrepareFinishHash = mRecordStream.PrepareToFinish();
-
             if (!mExpectSessionTicket)
             {
                 SendChangeCipherSpecMessage();
@@ -669,6 +672,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 }
             }
 
+            if (mSecurityParameters.extendedMasterSecret)
+            {
+                this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(mServerExtensions);
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(mServerExtensions);
+            }
+
             /*
              * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
              * extensions appearing in the client hello, and Send a server hello containing no
diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs
index f1ea0996d..d571e5900 100644
--- a/crypto/src/crypto/tls/TlsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsUtilities.cs
@@ -740,9 +740,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             int prfAlgorithm = context.SecurityParameters.PrfAlgorithm;
 
             if (prfAlgorithm == PrfAlgorithm.tls_prf_legacy)
-            {
                 return PRF_legacy(secret, label, labelSeed, size);
-            }
 
             IDigest prfDigest = CreatePrfHash(prfAlgorithm);
             byte[] buf = new byte[size];
@@ -817,9 +815,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                     DerBitString ku = KeyUsage.GetInstance(ext);
                     int bits = ku.GetBytes()[0];
                     if ((bits & keyUsageBits) != keyUsageBits)
-                    {
                         throw new TlsFatalAlert(AlertDescription.certificate_unknown);
-                    }
                 }
             }
         }
@@ -831,9 +827,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             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);
         }
@@ -870,14 +864,19 @@ namespace Org.BouncyCastle.Crypto.Tls
         internal static byte[] CalculateMasterSecret(TlsContext context, byte[] pre_master_secret)
         {
             SecurityParameters securityParameters = context.SecurityParameters;
-            byte[] seed = Concat(securityParameters.ClientRandom, securityParameters.ServerRandom);
+
+            byte[] seed = securityParameters.extendedMasterSecret
+                ?   securityParameters.SessionHash
+                :   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);
+            string asciiLabel = securityParameters.extendedMasterSecret
+                ?   ExporterLabel.extended_master_secret
+                :   ExporterLabel.master_secret;
+
+            return PRF(context, pre_master_secret, asciiLabel, seed, 48);
         }
 
         internal static byte[] CalculateMasterSecret_Ssl(byte[] pre_master_secret, byte[] random)
@@ -912,9 +911,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         internal static byte[] CalculateVerifyData(TlsContext context, string asciiLabel, byte[] handshakeHash)
         {
             if (IsSsl(context))
-            {
                 return handshakeHash;
-            }
 
             SecurityParameters securityParameters = context.SecurityParameters;
             byte[] master_secret = securityParameters.MasterSecret;
diff --git a/crypto/src/openssl/PEMReader.cs b/crypto/src/openssl/PEMReader.cs
index 9d3560838..8c19fe601 100644
--- a/crypto/src/openssl/PEMReader.cs
+++ b/crypto/src/openssl/PEMReader.cs
@@ -276,7 +276,7 @@ namespace Org.BouncyCastle.OpenSsl
                         if (seq.Count != 9)
                             throw new PemException("malformed sequence in RSA private key");
 
-                        RsaPrivateKeyStructure rsa = new RsaPrivateKeyStructure(seq);
+                        RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(seq);
 
                         pubSpec = new RsaKeyParameters(false, rsa.Modulus, rsa.PublicExponent);
                         privSpec = new RsaPrivateCrtKeyParameters(
diff --git a/crypto/src/pkcs/Pkcs12Store.cs b/crypto/src/pkcs/Pkcs12Store.cs
index 40364eec7..e4fe29401 100644
--- a/crypto/src/pkcs/Pkcs12Store.cs
+++ b/crypto/src/pkcs/Pkcs12Store.cs
@@ -28,6 +28,8 @@ namespace Org.BouncyCastle.Pkcs
         private readonly DerObjectIdentifier	certAlgorithm;
         private readonly bool					useDerEncoding;
 
+        private AsymmetricKeyEntry unmarkedKeyEntry = null;
+
         private const int MinIterations = 1024;
         private const int SaltSize = 20;
 
@@ -107,22 +109,101 @@ namespace Org.BouncyCastle.Pkcs
             Load(input, password);
         }
 
+        protected virtual void LoadKeyBag(PrivateKeyInfo privKeyInfo, Asn1Set bagAttributes)
+        {
+            AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privKeyInfo);
+
+            IDictionary attributes = Platform.CreateHashtable();
+            AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(privKey, attributes);
+
+            string alias = null;
+            Asn1OctetString localId = null;
+
+            if (bagAttributes != null)
+            {
+                foreach (Asn1Sequence sq in bagAttributes)
+                {
+                    DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]);
+                    Asn1Set attrSet = Asn1Set.GetInstance(sq[1]);
+                    Asn1Encodable attr = null;
+
+                    if (attrSet.Count > 0)
+                    {
+                        // TODO We should be adding all attributes in the set
+                        attr = attrSet[0];
+
+                        // TODO We might want to "merge" attribute sets with
+                        // the same OID - currently, differing values give an error
+                        if (attributes.Contains(aOid.Id))
+                        {
+                            // OK, but the value has to be the same
+                            if (!attributes[aOid.Id].Equals(attr))
+                                throw new IOException("attempt to add existing attribute with different value");
+                        }
+                        else
+                        {
+                            attributes.Add(aOid.Id, attr);
+                        }
+
+                        if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
+                        {
+                            alias = ((DerBmpString)attr).GetString();
+                            // TODO Do these in a separate loop, just collect aliases here
+                            keys[alias] = keyEntry;
+                        }
+                        else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
+                        {
+                            localId = (Asn1OctetString)attr;
+                        }
+                    }
+                }
+            }
+
+            if (localId != null)
+            {
+                string name = Hex.ToHexString(localId.GetOctets());
+
+                if (alias == null)
+                {
+                    keys[name] = keyEntry;
+                }
+                else
+                {
+                    // TODO There may have been more than one alias
+                    localIds[alias] = name;
+                }
+            }
+            else
+            {
+                unmarkedKeyEntry = keyEntry;
+            }
+        }
+
+        protected virtual void LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo encPrivKeyInfo, Asn1Set bagAttributes,
+            char[] password, bool wrongPkcs12Zero)
+        {
+            if (password != null)
+            {
+                PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(
+                    password, wrongPkcs12Zero, encPrivKeyInfo);
+
+                LoadKeyBag(privInfo, bagAttributes);
+            }
+        }
+
         public void Load(
             Stream	input,
             char[]	password)
         {
             if (input == null)
                 throw new ArgumentNullException("input");
-            if (password == null)
-                throw new ArgumentNullException("password");
 
             Asn1Sequence obj = (Asn1Sequence) Asn1Object.FromStream(input);
             Pfx bag = new Pfx(obj);
             ContentInfo info = bag.AuthSafe;
-            bool unmarkedKey = false;
             bool wrongPkcs12Zero = false;
 
-            if (bag.MacData != null) // check the mac code
+            if (password != null && bag.MacData != null) // check the mac code
             {
                 MacData mData = bag.MacData;
                 DigestInfo dInfo = mData.Mac;
@@ -152,8 +233,9 @@ namespace Org.BouncyCastle.Pkcs
 
             keys.Clear();
             localIds.Clear();
+            unmarkedKeyEntry = null;
 
-            IList chain = Platform.CreateArrayList();
+            IList certBags = Platform.CreateArrayList();
 
             if (info.ContentType.Equals(PkcsObjectIdentifiers.Data))
             {
@@ -166,109 +248,28 @@ namespace Org.BouncyCastle.Pkcs
                 {
                     DerObjectIdentifier oid = ci.ContentType;
 
+                    byte[] octets = null;
                     if (oid.Equals(PkcsObjectIdentifiers.Data))
                     {
-                        byte[] octets = ((Asn1OctetString)ci.Content).GetOctets();
-                        Asn1Sequence seq = (Asn1Sequence) Asn1Object.FromByteArray(octets);
-
-                        foreach (Asn1Sequence subSeq in seq)
+                        octets = ((Asn1OctetString)ci.Content).GetOctets();
+                    }
+                    else if (oid.Equals(PkcsObjectIdentifiers.EncryptedData))
+                    {
+                        if (password != null)
                         {
-                            SafeBag b = new SafeBag(subSeq);
-
-                            if (b.BagID.Equals(PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag))
-                            {
-                                EncryptedPrivateKeyInfo eIn = EncryptedPrivateKeyInfo.GetInstance(b.BagValue);
-                                PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(
-                                    password, wrongPkcs12Zero, eIn);
-                                AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privInfo);
-
-                                //
-                                // set the attributes on the key
-                                //
-                                IDictionary attributes = Platform.CreateHashtable();
-                                AsymmetricKeyEntry pkcs12Key = new AsymmetricKeyEntry(privKey, attributes);
-                                string alias = null;
-                                Asn1OctetString localId = null;
-
-                                if (b.BagAttributes != null)
-                                {
-                                    foreach (Asn1Sequence sq in b.BagAttributes)
-                                    {
-                                        DerObjectIdentifier aOid = (DerObjectIdentifier) sq[0];
-                                        Asn1Set attrSet = (Asn1Set) sq[1];
-                                        Asn1Encodable attr = null;
-
-                                        if (attrSet.Count > 0)
-                                        {
-                                            // TODO We should be adding all attributes in the set
-                                            attr = attrSet[0];
-
-                                            // TODO We might want to "merge" attribute sets with
-                                            // the same OID - currently, differing values give an error
-                                            if (attributes.Contains(aOid.Id))
-                                            {
-                                                // OK, but the value has to be the same
-                                                if (!attributes[aOid.Id].Equals(attr))
-                                                {
-                                                    throw new IOException("attempt to add existing attribute with different value");
-                                                }
-                                            }
-                                            else
-                                            {
-                                                attributes.Add(aOid.Id, attr);
-                                            }
-
-                                            if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
-                                            {
-                                                alias = ((DerBmpString)attr).GetString();
-                                                // TODO Do these in a separate loop, just collect aliases here
-                                                keys[alias] = pkcs12Key;
-                                            }
-                                            else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
-                                            {
-                                                localId = (Asn1OctetString)attr;
-                                            }
-                                        }
-                                    }
-                                }
-
-                                if (localId != null)
-                                {
-                                    string name = Hex.ToHexString(localId.GetOctets());
-
-                                    if (alias == null)
-                                    {
-                                        keys[name] = pkcs12Key;
-                                    }
-                                    else
-                                    {
-                                        // TODO There may have been more than one alias
-                                        localIds[alias] = name;
-                                    }
-                                }
-                                else
-                                {
-                                    unmarkedKey = true;
-                                    keys["unmarked"] = pkcs12Key;
-                                }
-                            }
-                            else if (b.BagID.Equals(PkcsObjectIdentifiers.CertBag))
-                            {
-                                chain.Add(b);
-                            }
-                            else
-                            {
-                                Console.WriteLine("extra " + b.BagID);
-                                Console.WriteLine("extra " + Asn1Dump.DumpAsString(b));
-                            }
+                            EncryptedData d = EncryptedData.GetInstance(ci.Content);
+                            octets = CryptPbeData(false, d.EncryptionAlgorithm,
+                                password, wrongPkcs12Zero, d.Content.GetOctets());
                         }
                     }
-                    else if (oid.Equals(PkcsObjectIdentifiers.EncryptedData))
+                    else
+                    {
+                        // TODO Other data types
+                    }
+
+                    if (octets != null)
                     {
-                        EncryptedData d = EncryptedData.GetInstance(ci.Content);
-                        byte[] octets = CryptPbeData(false, d.EncryptionAlgorithm,
-                            password, wrongPkcs12Zero, d.Content.GetOctets());
-                        Asn1Sequence seq = (Asn1Sequence) Asn1Object.FromByteArray(octets);
+                        Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(octets);
 
                         foreach (Asn1Sequence subSeq in seq)
                         {
@@ -276,156 +277,23 @@ namespace Org.BouncyCastle.Pkcs
 
                             if (b.BagID.Equals(PkcsObjectIdentifiers.CertBag))
                             {
-                                chain.Add(b);
+                                certBags.Add(b);
                             }
                             else if (b.BagID.Equals(PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag))
                             {
-                                EncryptedPrivateKeyInfo eIn = EncryptedPrivateKeyInfo.GetInstance(b.BagValue);
-                                PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(
-                                    password, wrongPkcs12Zero, eIn);
-                                AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privInfo);
-
-                                //
-                                // set the attributes on the key
-                                //
-                                IDictionary attributes = Platform.CreateHashtable();
-                                AsymmetricKeyEntry pkcs12Key = new AsymmetricKeyEntry(privKey, attributes);
-                                string alias = null;
-                                Asn1OctetString localId = null;
-
-                                foreach (Asn1Sequence sq in b.BagAttributes)
-                                {
-                                    DerObjectIdentifier aOid = (DerObjectIdentifier) sq[0];
-                                    Asn1Set attrSet = (Asn1Set) sq[1];
-                                    Asn1Encodable attr = null;
-
-                                    if (attrSet.Count > 0)
-                                    {
-                                        // TODO We should be adding all attributes in the set
-                                        attr = attrSet[0];
-
-                                        // TODO We might want to "merge" attribute sets with
-                                        // the same OID - currently, differing values give an error
-                                        if (attributes.Contains(aOid.Id))
-                                        {
-                                            // OK, but the value has to be the same
-                                            if (!attributes[aOid.Id].Equals(attr))
-                                            {
-                                                throw new IOException("attempt to add existing attribute with different value");
-                                            }
-                                        }
-                                        else
-                                        {
-                                            attributes.Add(aOid.Id, attr);
-                                        }
-
-                                        if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
-                                        {
-                                            alias = ((DerBmpString)attr).GetString();
-                                            // TODO Do these in a separate loop, just collect aliases here
-                                            keys[alias] = pkcs12Key;
-                                        }
-                                        else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
-                                        {
-                                            localId = (Asn1OctetString)attr;
-                                        }
-                                    }
-                                }
-
-                                // TODO Should we be checking localIds != null here
-                                // as for PkcsObjectIdentifiers.Data version above?
-
-                                string name = Hex.ToHexString(localId.GetOctets());
-
-                                if (alias == null)
-                                {
-                                    keys[name] = pkcs12Key;
-                                }
-                                else
-                                {
-                                    // TODO There may have been more than one alias
-                                    localIds[alias] = name;
-                                }
+                                LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo.GetInstance(b.BagValue),
+                                    b.BagAttributes, password, wrongPkcs12Zero);
                             }
                             else if (b.BagID.Equals(PkcsObjectIdentifiers.KeyBag))
                             {
-                                PrivateKeyInfo privKeyInfo = PrivateKeyInfo.GetInstance(b.BagValue);
-                                AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privKeyInfo);
-
-                                //
-                                // set the attributes on the key
-                                //
-                                string alias = null;
-                                Asn1OctetString localId = null;
-                                IDictionary attributes = Platform.CreateHashtable();
-                                AsymmetricKeyEntry pkcs12Key = new AsymmetricKeyEntry(privKey, attributes);
-
-                                foreach (Asn1Sequence sq in b.BagAttributes)
-                                {
-                                    DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]);
-                                    Asn1Set attrSet = Asn1Set.GetInstance(sq[1]);
-                                    Asn1Encodable attr = null;
-
-                                    if (attrSet.Count > 0)
-                                    {
-                                        // TODO We should be adding all attributes in the set
-                                        attr = attrSet[0];
-
-                                        // TODO We might want to "merge" attribute sets with
-                                        // the same OID - currently, differing values give an error
-                                        if (attributes.Contains(aOid.Id))
-                                        {
-                                            // OK, but the value has to be the same
-                                            if (!attributes[aOid.Id].Equals(attr))
-                                            {
-                                                throw new IOException("attempt to add existing attribute with different value");
-                                            }
-                                        }
-                                        else
-                                        {
-                                            attributes.Add(aOid.Id, attr);
-                                        }
-
-                                        if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
-                                        {
-                                            alias = ((DerBmpString)attr).GetString();
-                                            // TODO Do these in a separate loop, just collect aliases here
-                                            keys[alias] = pkcs12Key;
-                                        }
-                                        else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
-                                        {
-                                            localId = (Asn1OctetString)attr;
-                                        }
-                                    }
-                                }
-
-                                // TODO Should we be checking localIds != null here
-                                // as for PkcsObjectIdentifiers.Data version above?
-
-                                string name = Hex.ToHexString(localId.GetOctets());
-
-                                if (alias == null)
-                                {
-                                    keys[name] = pkcs12Key;
-                                }
-                                else
-                                {
-                                    // TODO There may have been more than one alias
-                                    localIds[alias] = name;
-                                }
+                                LoadKeyBag(PrivateKeyInfo.GetInstance(b.BagValue), b.BagAttributes);
                             }
                             else
                             {
-                                Console.WriteLine("extra " + b.BagID);
-                                Console.WriteLine("extra " + Asn1Dump.DumpAsString(b));
+                                // TODO Other bag types
                             }
                         }
                     }
-                    else
-                    {
-                        Console.WriteLine("extra " + oid);
-                        Console.WriteLine("extra " + Asn1Dump.DumpAsString(ci.Content));
-                    }
                 }
             }
 
@@ -433,10 +301,10 @@ namespace Org.BouncyCastle.Pkcs
             chainCerts.Clear();
             keyCerts.Clear();
 
-            foreach (SafeBag b in chain)
+            foreach (SafeBag b in certBags)
             {
-                CertBag cb = new CertBag((Asn1Sequence)b.BagValue);
-                byte[] octets = ((Asn1OctetString) cb.CertValue).GetOctets();
+                CertBag certBag = new CertBag((Asn1Sequence)b.BagValue);
+                byte[] octets = ((Asn1OctetString)certBag.CertValue).GetOctets();
                 X509Certificate cert = new X509CertificateParser().ReadCertificate(octets);
 
                 //
@@ -486,21 +354,18 @@ namespace Org.BouncyCastle.Pkcs
                 }
 
                 CertId certId = new CertId(cert.GetPublicKey());
-                X509CertificateEntry pkcs12Cert = new X509CertificateEntry(cert, attributes);
+                X509CertificateEntry certEntry = new X509CertificateEntry(cert, attributes);
 
-                chainCerts[certId] = pkcs12Cert;
+                chainCerts[certId] = certEntry;
 
-                if (unmarkedKey)
+                if (unmarkedKeyEntry != null)
                 {
                     if (keyCerts.Count == 0)
                     {
                         string name = Hex.ToHexString(certId.Id);
 
-                        keyCerts[name] = pkcs12Cert;
-
-                        object temp = keys["unmarked"];
-                        keys.Remove("unmarked");
-                        keys[name] = temp;
+                        keyCerts[name] = certEntry;
+                        keys[name] = unmarkedKeyEntry;
                     }
                 }
                 else
@@ -509,13 +374,13 @@ namespace Org.BouncyCastle.Pkcs
                     {
                         string name = Hex.ToHexString(localId.GetOctets());
 
-                        keyCerts[name] = pkcs12Cert;
+                        keyCerts[name] = certEntry;
                     }
 
                     if (alias != null)
                     {
                         // TODO There may have been more than one alias
-                        certs[alias] = pkcs12Cert;
+                        certs[alias] = certEntry;
                     }
                 }
             }
@@ -841,24 +706,34 @@ namespace Org.BouncyCastle.Pkcs
         {
             if (stream == null)
                 throw new ArgumentNullException("stream");
-            if (password == null)
-                throw new ArgumentNullException("password");
             if (random == null)
                 throw new ArgumentNullException("random");
 
             //
-            // handle the key
+            // handle the keys
             //
-            Asn1EncodableVector keyS = new Asn1EncodableVector();
+            Asn1EncodableVector keyBags = new Asn1EncodableVector();
             foreach (string name in keys.Keys)
             {
                 byte[] kSalt = new byte[SaltSize];
                 random.NextBytes(kSalt);
 
-                AsymmetricKeyEntry privKey = (AsymmetricKeyEntry) keys[name];
-                EncryptedPrivateKeyInfo kInfo =
-                    EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo(
-                    keyAlgorithm, password, kSalt, MinIterations, privKey.Key);
+                AsymmetricKeyEntry privKey = (AsymmetricKeyEntry)keys[name];
+
+                DerObjectIdentifier bagOid;
+                Asn1Encodable bagData;
+
+                if (password == null)
+                {
+                    bagOid = PkcsObjectIdentifiers.KeyBag;
+                    bagData = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privKey.Key);
+                }
+                else
+                {
+                    bagOid = PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag;
+                    bagData = EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo(
+                        keyAlgorithm, password, kSalt, MinIterations, privKey.Key);
+                }
 
                 Asn1EncodableVector kName = new Asn1EncodableVector();
 
@@ -903,13 +778,11 @@ namespace Org.BouncyCastle.Pkcs
                             new DerSet(subjectKeyID)));
                 }
 
-                SafeBag kBag = new SafeBag(PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag, kInfo.ToAsn1Object(), new DerSet(kName));
-                keyS.Add(kBag);
+                keyBags.Add(new SafeBag(bagOid, bagData.ToAsn1Object(), new DerSet(kName)));
             }
 
-            byte[] derEncodedBytes = new DerSequence(keyS).GetDerEncoded();
-
-            BerOctetString keyString = new BerOctetString(derEncodedBytes);
+            byte[] keyBagsEncoding = new DerSequence(keyBags).GetDerEncoded();
+            ContentInfo keysInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(keyBagsEncoding));
 
             //
             // certificate processing
@@ -918,7 +791,7 @@ namespace Org.BouncyCastle.Pkcs
 
             random.NextBytes(cSalt);
 
-            Asn1EncodableVector	certSeq = new Asn1EncodableVector();
+            Asn1EncodableVector	certBags = new Asn1EncodableVector();
             Pkcs12PbeParams		cParams = new Pkcs12PbeParams(cSalt, MinIterations);
             AlgorithmIdentifier	cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.ToAsn1Object());
             ISet				doneCerts = new HashSet();
@@ -972,10 +845,7 @@ namespace Org.BouncyCastle.Pkcs
                             new DerSet(subjectKeyID)));
                 }
 
-                SafeBag sBag = new SafeBag(
-                    PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName));
-
-                certSeq.Add(sBag);
+                certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
 
                 doneCerts.Add(certEntry.Certificate);
             }
@@ -1026,10 +896,7 @@ namespace Org.BouncyCastle.Pkcs
                             new DerSet(new DerBmpString(certId))));
                 }
 
-                SafeBag sBag = new SafeBag(PkcsObjectIdentifiers.CertBag,
-                    cBag.ToAsn1Object(), new DerSet(fName));
-
-                certSeq.Add(sBag);
+                certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
 
                 doneCerts.Add(cert.Certificate);
             }
@@ -1062,22 +929,24 @@ namespace Org.BouncyCastle.Pkcs
                             new DerSet(cert[oid])));
                 }
 
-                SafeBag sBag = new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName));
-
-                certSeq.Add(sBag);
+                certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
             }
 
-            derEncodedBytes = new DerSequence(certSeq).GetDerEncoded();
-
-            byte[] certBytes = CryptPbeData(true, cAlgId, password, false, derEncodedBytes);
+            byte[] certBagsEncoding = new DerSequence(certBags).GetDerEncoded();
 
-            EncryptedData cInfo = new EncryptedData(PkcsObjectIdentifiers.Data, cAlgId, new BerOctetString(certBytes));
-
-            ContentInfo[] info = new ContentInfo[]
+            ContentInfo certsInfo;
+            if (password == null)
             {
-                new ContentInfo(PkcsObjectIdentifiers.Data, keyString),
-                new ContentInfo(PkcsObjectIdentifiers.EncryptedData, cInfo.ToAsn1Object())
-            };
+                certsInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(certBagsEncoding));
+            }
+            else
+            {
+                byte[] certBytes = CryptPbeData(true, cAlgId, password, false, certBagsEncoding);
+                EncryptedData cInfo = new EncryptedData(PkcsObjectIdentifiers.Data, cAlgId, new BerOctetString(certBytes));
+                certsInfo = new ContentInfo(PkcsObjectIdentifiers.EncryptedData, cInfo.ToAsn1Object());
+            }
+
+            ContentInfo[] info = new ContentInfo[]{ keysInfo, certsInfo };
 
             byte[] data = new AuthenticatedSafe(info).GetEncoded(
                 useDerEncoding ? Asn1Encodable.Der : Asn1Encodable.Ber);
@@ -1087,22 +956,26 @@ namespace Org.BouncyCastle.Pkcs
             //
             // create the mac
             //
-            byte[] mSalt = new byte[20];
-            random.NextBytes(mSalt);
+            MacData macData = null;
+            if (password != null)
+            {
+                byte[] mSalt = new byte[20];
+                random.NextBytes(mSalt);
 
-            byte[] mac = CalculatePbeMac(OiwObjectIdentifiers.IdSha1,
-                mSalt, MinIterations, password, false, data);
+                byte[] mac = CalculatePbeMac(OiwObjectIdentifiers.IdSha1,
+                    mSalt, MinIterations, password, false, data);
 
-            AlgorithmIdentifier algId = new AlgorithmIdentifier(
-                OiwObjectIdentifiers.IdSha1, DerNull.Instance);
-            DigestInfo dInfo = new DigestInfo(algId, mac);
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(
+                    OiwObjectIdentifiers.IdSha1, DerNull.Instance);
+                DigestInfo dInfo = new DigestInfo(algId, mac);
 
-            MacData mData = new MacData(dInfo, mSalt, MinIterations);
+                macData = new MacData(dInfo, mSalt, MinIterations);
+            }
 
             //
             // output the Pfx
             //
-            Pfx pfx = new Pfx(mainInfo, mData);
+            Pfx pfx = new Pfx(mainInfo, macData);
 
             DerOutputStream derOut;
             if (useDerEncoding)
diff --git a/crypto/src/security/PrivateKeyFactory.cs b/crypto/src/security/PrivateKeyFactory.cs
index 1cfa37afe..edc5ef85a 100644
--- a/crypto/src/security/PrivateKeyFactory.cs
+++ b/crypto/src/security/PrivateKeyFactory.cs
@@ -53,8 +53,7 @@ namespace Org.BouncyCastle.Security
                 || algOid.Equals(PkcsObjectIdentifiers.IdRsassaPss)
                 || algOid.Equals(PkcsObjectIdentifiers.IdRsaesOaep))
             {
-                RsaPrivateKeyStructure keyStructure = new RsaPrivateKeyStructure(
-                    Asn1Sequence.GetInstance(keyInfo.ParsePrivateKey()));
+                RsaPrivateKeyStructure keyStructure = RsaPrivateKeyStructure.GetInstance(keyInfo.ParsePrivateKey());
 
                 return new RsaPrivateCrtKeyParameters(
                     keyStructure.Modulus,
diff --git a/crypto/src/security/SecureRandom.cs b/crypto/src/security/SecureRandom.cs
index ac9d98158..f46427a5c 100644
--- a/crypto/src/security/SecureRandom.cs
+++ b/crypto/src/security/SecureRandom.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Threading;
 
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
@@ -8,221 +9,247 @@ using Org.BouncyCastle.Utilities;
 namespace Org.BouncyCastle.Security
 {
     public class SecureRandom
-		: Random
+        : Random
     {
-		// Note: all objects of this class should be deriving their random data from
-		// a single generator appropriate to the digest being used.
-		private static readonly IRandomGenerator sha1Generator = new DigestRandomGenerator(new Sha1Digest());
-		private static readonly IRandomGenerator sha256Generator = new DigestRandomGenerator(new Sha256Digest());
-
-		private static readonly SecureRandom[] master = { null };
-		private static SecureRandom Master
-		{
-			get
-			{
-				if (master[0] == null)
-				{
-					IRandomGenerator gen = sha256Generator;
-					gen = new ReversedWindowGenerator(gen, 32);
-					SecureRandom sr = master[0] = new SecureRandom(gen);
-
-					sr.SetSeed(DateTime.Now.Ticks);
-					sr.SetSeed(new ThreadedSeedGenerator().GenerateSeed(24, true));
-					sr.GenerateSeed(1 + sr.Next(32));
-				}
-
-				return master[0];
-			}
-		}
-
-		public static SecureRandom GetInstance(
-			string algorithm)
-		{
-			// TODO Compared to JDK, we don't auto-seed if the client forgets - problem?
-
-			// TODO Support all digests more generally, by stripping PRNG and calling DigestUtilities?
-			string drgName = Platform.ToUpperInvariant(algorithm);
-
-			IRandomGenerator drg = null;
-			if (drgName == "SHA1PRNG")
-			{
-				drg = sha1Generator;
-			}
-			else if (drgName == "SHA256PRNG")
-			{
-				drg = sha256Generator;
-			}
-
-			if (drg != null)
-			{
-				return new SecureRandom(drg);
-			}
-
-			throw new ArgumentException("Unrecognised PRNG algorithm: " + algorithm, "algorithm");
-		}
-
-		public static byte[] GetSeed(
-			int length)
-		{
-			return Master.GenerateSeed(length);
-		}
-
-		protected IRandomGenerator generator;
-
-		public SecureRandom()
-			: this(sha1Generator)
-        {
-			SetSeed(GetSeed(8));
-		}
-
-		public SecureRandom(
-			byte[] inSeed)
-			: this(sha1Generator)
-        {
-			SetSeed(inSeed);
-        }
-
-		/// <summary>Use the specified instance of IRandomGenerator as random source.</summary>
-		/// <remarks>
-		/// This constructor performs no seeding of either the <c>IRandomGenerator</c> or the
-		/// constructed <c>SecureRandom</c>. It is the responsibility of the client to provide
-		/// proper seed material as necessary/appropriate for the given <c>IRandomGenerator</c>
-		/// implementation.
-		/// </remarks>
-		/// <param name="generator">The source to generate all random bytes from.</param>
-		public SecureRandom(
-			IRandomGenerator generator)
-			: base(0)
-		{
-			this.generator = generator;
-		}
-
-		public virtual byte[] GenerateSeed(
-			int length)
-		{
-			SetSeed(DateTime.Now.Ticks);
-
-			byte[] rv = new byte[length];
-			NextBytes(rv);
-			return rv;
-		}
-
-		public virtual void SetSeed(
-			byte[] inSeed)
-        {
-			generator.AddSeedMaterial(inSeed);
-        }
-
-        public virtual void SetSeed(
-			long seed)
-        {
-			generator.AddSeedMaterial(seed);
-		}
-
-		public override int Next()
-		{
-			for (;;)
-			{
-				int i = NextInt() & int.MaxValue;
-
-				if (i != int.MaxValue)
-					return i;
-			}
-		}
-
-		public override int Next(
-			int maxValue)
-		{
-			if (maxValue < 2)
-			{
-				if (maxValue < 0)
-					throw new ArgumentOutOfRangeException("maxValue", "cannot be negative");
-
-				return 0;
-			}
-
-			// Test whether maxValue is a power of 2
-			if ((maxValue & -maxValue) == maxValue)
-			{
-				int val = NextInt() & int.MaxValue;
-				long lr = ((long) maxValue * (long) val) >> 31;
-				return (int) lr;
-			}
-
-			int bits, result;
-			do
-			{
-				bits = NextInt() & int.MaxValue;
-				result = bits % maxValue;
-			}
-			while (bits - result + (maxValue - 1) < 0); // Ignore results near overflow
-
-			return result;
-		}
-
-		public override int Next(
-			int	minValue,
-			int	maxValue)
-		{
-			if (maxValue <= minValue)
-			{
-				if (maxValue == minValue)
-					return minValue;
-
-				throw new ArgumentException("maxValue cannot be less than minValue");
-			}
-
-			int diff = maxValue - minValue;
-			if (diff > 0)
-				return minValue + Next(diff);
-
-			for (;;)
-			{
-				int i = NextInt();
-
-				if (i >= minValue && i < maxValue)
-					return i;
-			}
-		}
-
-		public override void NextBytes(
-			byte[] buffer)
-        {
-			generator.NextBytes(buffer);
-        }
-
-		public virtual void NextBytes(
-			byte[]	buffer,
-			int		start,
-			int		length)
-		{
-			generator.NextBytes(buffer, start, length);
-		}
-
-		private static readonly double DoubleScale = System.Math.Pow(2.0, 64.0);
-
-		public override double NextDouble()
-		{
-			return Convert.ToDouble((ulong) NextLong()) / DoubleScale;
-		}
-
-		public virtual int NextInt()
-        {
-			byte[] intBytes = new byte[4];
+        private static long counter = Times.NanoTime();
+
+        private static long NextCounterValue()
+        {
+            return Interlocked.Increment(ref counter);
+        }
+
+#if NETCF_1_0
+        private static readonly SecureRandom[] master = { null };
+        private static SecureRandom Master
+        {
+            get
+            {
+                lock (master)
+                {
+                    if (master[0] == null)
+                    {
+                        SecureRandom sr = master[0] = GetInstance("SHA256PRNG", false);
+
+                        // Even though Ticks has at most 8 or 14 bits of entropy, there's no harm in adding it.
+                        sr.SetSeed(DateTime.Now.Ticks);
+
+                        // 32 will be enough when ThreadedSeedGenerator is fixed.  Until then, ThreadedSeedGenerator returns low
+                        // entropy, and this is not sufficient to be secure. http://www.bouncycastle.org/csharpdevmailarchive/msg00814.html
+                        sr.SetSeed(new ThreadedSeedGenerator().GenerateSeed(32, true));
+                    }
+
+                    return master[0];
+                }
+            }
+        }
+#else
+        private static readonly SecureRandom master = new SecureRandom(new CryptoApiRandomGenerator());
+        private static SecureRandom Master
+        {
+            get { return master; }
+        }
+#endif
+
+        private static DigestRandomGenerator CreatePrng(string digestName, bool autoSeed)
+        {
+            IDigest digest = DigestUtilities.GetDigest(digestName);
+            if (digest == null)
+                return null;
+            DigestRandomGenerator prng = new DigestRandomGenerator(digest);
+            if (autoSeed)
+            {
+                prng.AddSeedMaterial(NextCounterValue());
+                prng.AddSeedMaterial(GetSeed(digest.GetDigestSize()));
+            }
+            return prng;
+        }
+
+        /// <summary>
+        /// Create and auto-seed an instance based on the given algorithm.
+        /// </summary>
+        /// <remarks>Equivalent to GetInstance(algorithm, true)</remarks>
+        /// <param name="algorithm">e.g. "SHA256PRNG"</param>
+        public static SecureRandom GetInstance(string algorithm)
+        {
+            return GetInstance(algorithm, true);
+        }
+
+        /// <summary>
+        /// Create an instance based on the given algorithm, with optional auto-seeding
+        /// </summary>
+        /// <param name="algorithm">e.g. "SHA256PRNG"</param>
+        /// <param name="autoSeed">If true, the instance will be auto-seeded.</param>
+        public static SecureRandom GetInstance(string algorithm, bool autoSeed)
+        {
+            string upper = Platform.ToUpperInvariant(algorithm);
+            if (upper.EndsWith("PRNG"))
+            {
+                string digestName = upper.Substring(0, upper.Length - "PRNG".Length);
+                DigestRandomGenerator prng = CreatePrng(digestName, autoSeed);
+                if (prng != null)
+                {
+                    return new SecureRandom(prng);
+                }
+            }
+
+            throw new ArgumentException("Unrecognised PRNG algorithm: " + algorithm, "algorithm");
+        }
+
+        public static byte[] GetSeed(int length)
+        {
+#if NETCF_1_0
+            lock (master)
+#endif
+            return Master.GenerateSeed(length);
+        }
+
+        protected readonly IRandomGenerator generator;
+
+        public SecureRandom()
+            : this(CreatePrng("SHA256", true))
+        {
+        }
+
+        /// <remarks>
+        /// To replicate existing predictable output, replace with GetInstance("SHA1PRNG", false), followed by SetSeed(seed)
+        /// </remarks>
+        [Obsolete("Use GetInstance/SetSeed instead")]
+        public SecureRandom(byte[] seed)
+            : this(CreatePrng("SHA1", false))
+        {
+            SetSeed(seed);
+        }
+
+        /// <summary>Use the specified instance of IRandomGenerator as random source.</summary>
+        /// <remarks>
+        /// This constructor performs no seeding of either the <c>IRandomGenerator</c> or the
+        /// constructed <c>SecureRandom</c>. It is the responsibility of the client to provide
+        /// proper seed material as necessary/appropriate for the given <c>IRandomGenerator</c>
+        /// implementation.
+        /// </remarks>
+        /// <param name="generator">The source to generate all random bytes from.</param>
+        public SecureRandom(IRandomGenerator generator)
+            : base(0)
+        {
+            this.generator = generator;
+        }
+
+        public virtual byte[] GenerateSeed(int length)
+        {
+            SetSeed(DateTime.Now.Ticks);
+
+            byte[] rv = new byte[length];
+            NextBytes(rv);
+            return rv;
+        }
+
+        public virtual void SetSeed(byte[] seed)
+        {
+            generator.AddSeedMaterial(seed);
+        }
+
+        public virtual void SetSeed(long seed)
+        {
+            generator.AddSeedMaterial(seed);
+        }
+
+        public override int Next()
+        {
+            for (;;)
+            {
+                int i = NextInt() & int.MaxValue;
+
+                if (i != int.MaxValue)
+                    return i;
+            }
+        }
+
+        public override int Next(int maxValue)
+        {
+            if (maxValue < 2)
+            {
+                if (maxValue < 0)
+                    throw new ArgumentOutOfRangeException("maxValue", "cannot be negative");
+
+                return 0;
+            }
+
+            // Test whether maxValue is a power of 2
+            if ((maxValue & -maxValue) == maxValue)
+            {
+                int val = NextInt() & int.MaxValue;
+                long lr = ((long) maxValue * (long) val) >> 31;
+                return (int) lr;
+            }
+
+            int bits, result;
+            do
+            {
+                bits = NextInt() & int.MaxValue;
+                result = bits % maxValue;
+            }
+            while (bits - result + (maxValue - 1) < 0); // Ignore results near overflow
+
+            return result;
+        }
+
+        public override int Next(int minValue, int maxValue)
+        {
+            if (maxValue <= minValue)
+            {
+                if (maxValue == minValue)
+                    return minValue;
+
+                throw new ArgumentException("maxValue cannot be less than minValue");
+            }
+
+            int diff = maxValue - minValue;
+            if (diff > 0)
+                return minValue + Next(diff);
+
+            for (;;)
+            {
+                int i = NextInt();
+
+                if (i >= minValue && i < maxValue)
+                    return i;
+            }
+        }
+
+        public override void NextBytes(byte[] buf)
+        {
+            generator.NextBytes(buf);
+        }
+
+        public virtual void NextBytes(byte[] buf, int off, int len)
+        {
+            generator.NextBytes(buf, off, len);
+        }
+
+        private static readonly double DoubleScale = System.Math.Pow(2.0, 64.0);
+
+        public override double NextDouble()
+        {
+            return Convert.ToDouble((ulong) NextLong()) / DoubleScale;
+        }
+
+        public virtual int NextInt()
+        {
+            byte[] intBytes = new byte[4];
             NextBytes(intBytes);
 
-			int result = 0;
+            int result = 0;
             for (int i = 0; i < 4; i++)
             {
                 result = (result << 8) + (intBytes[i] & 0xff);
             }
 
-			return result;
+            return result;
         }
 
-		public virtual long NextLong()
-		{
-			return ((long)(uint) NextInt() << 32) | (long)(uint) NextInt();
-		}
+        public virtual long NextLong()
+        {
+            return ((long)(uint) NextInt() << 32) | (long)(uint) NextInt();
+        }
     }
 }
diff --git a/crypto/test/src/crypto/test/OAEPTest.cs b/crypto/test/src/crypto/test/OAEPTest.cs
index b4c375eec..ee48a183d 100644
--- a/crypto/test/src/crypto/test/OAEPTest.cs
+++ b/crypto/test/src/crypto/test/OAEPTest.cs
@@ -315,8 +315,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             // extract the private key info.
             //
             Asn1Object privKeyObj = Asn1Object.FromByteArray(privKeyEnc);
-            RsaPrivateKeyStructure privStruct = new RsaPrivateKeyStructure(
-                (Asn1Sequence)PrivateKeyInfo.GetInstance(privKeyObj).ParsePrivateKey());
+            RsaPrivateKeyStructure privStruct = RsaPrivateKeyStructure.GetInstance(
+                PrivateKeyInfo.GetInstance(privKeyObj).ParsePrivateKey());
 
             RsaKeyParameters pubParameters = new RsaKeyParameters(
                 false,
diff --git a/crypto/test/src/crypto/tls/test/MockTlsClient.cs b/crypto/test/src/crypto/tls/test/MockTlsClient.cs
index 61521adca..a458e32e6 100644
--- a/crypto/test/src/crypto/tls/test/MockTlsClient.cs
+++ b/crypto/test/src/crypto/tls/test/MockTlsClient.cs
@@ -26,8 +26,8 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
         public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause)
         {
             TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
-            output.WriteLine("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
-                + ")");
+            output.WriteLine("TLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
             if (message != null)
             {
                 output.WriteLine("> " + message);
@@ -41,8 +41,8 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
         public override void NotifyAlertReceived(byte alertLevel, byte alertDescription)
         {
             TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
-            output.WriteLine("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
-                + alertDescription + ")");
+            output.WriteLine("TLS client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
         }
 
         public override int[] GetCipherSuites()
@@ -62,6 +62,8 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
         {
             IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(base.GetClientExtensions());
             TlsExtensionsUtilities.AddEncryptThenMacExtension(clientExtensions);
+            // TODO[draft-ietf-tls-session-hash-01] Enable once code-point assigned (only for compatible server though)
+//            TlsExtensionsUtilities.AddExtendedMasterSecretExtension(clientExtensions);
             TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
             TlsExtensionsUtilities.AddTruncatedHMacExtension(clientExtensions);
             return clientExtensions;
diff --git a/crypto/test/src/crypto/tls/test/MockTlsServer.cs b/crypto/test/src/crypto/tls/test/MockTlsServer.cs
index 54a645d96..14d6b9839 100644
--- a/crypto/test/src/crypto/tls/test/MockTlsServer.cs
+++ b/crypto/test/src/crypto/tls/test/MockTlsServer.cs
@@ -13,8 +13,8 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
         public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause)
         {
             TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
-            output.WriteLine("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
-                + ")");
+            output.WriteLine("TLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
             if (message != null)
             {
                 output.WriteLine("> " + message);
@@ -28,8 +28,8 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
         public override void NotifyAlertReceived(byte alertLevel, byte alertDescription)
         {
             TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
-            output.WriteLine("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
-                + alertDescription + ")");
+            output.WriteLine("TLS server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
         }
 
         protected override int[] GetCipherSuites()
diff --git a/crypto/test/src/security/test/SecureRandomTest.cs b/crypto/test/src/security/test/SecureRandomTest.cs
index 12e4b9a47..4f05a286a 100644
--- a/crypto/test/src/security/test/SecureRandomTest.cs
+++ b/crypto/test/src/security/test/SecureRandomTest.cs
@@ -1,150 +1,165 @@
 using System;
+using System.Text;
 
 using NUnit.Framework;
 
 using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Security.Tests
 {
-	[TestFixture]
-	public class SecureRandomTest
-	{
+    [TestFixture]
+    public class SecureRandomTest
+    {
 #if !NETCF_1_0
-		[Test]
-		public void TestCryptoApi()
-		{
-			SecureRandom random = new SecureRandom(
-				new CryptoApiRandomGenerator());
-
-			checkSecureRandom(random);
-		}
+        [Test]
+        public void TestCryptoApi()
+        {
+            SecureRandom random = new SecureRandom(
+                new CryptoApiRandomGenerator());
+
+            CheckSecureRandom(random);
+        }
 #endif
 
-		[Test]
-		public void TestDefault()
-		{
-			SecureRandom random = new SecureRandom();
-
-			checkSecureRandom(random);
-		}
-
-		[Test]
-		public void TestSha1Prng()
-		{
-			SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
-			random.SetSeed(SecureRandom.GetSeed(20));
-
-			checkSecureRandom(random);
-		}
-
-		[Test]
-		public void TestSha256Prng()
-		{
-			SecureRandom random = SecureRandom.GetInstance("SHA256PRNG");
-			random.SetSeed(SecureRandom.GetSeed(32));
-
-			checkSecureRandom(random);
-		}
-
-		[Test]
-		public void TestThreadedSeed()
-		{
-			SecureRandom random = new SecureRandom(
-				new ThreadedSeedGenerator().GenerateSeed(20, false));
-
-			checkSecureRandom(random);
-		}
-
-		[Test]
-		public void TestVmpcPrng()
-		{
-			SecureRandom random = new SecureRandom(new VmpcRandomGenerator());
-			random.SetSeed(SecureRandom.GetSeed(32));
-
-			checkSecureRandom(random);
-		}
-
-
-		private static void checkSecureRandom(
-			SecureRandom random)
-		{
-			// Note: This will periodically (< 1e-6 probability) give a false alarm.
-			// That's randomness for you!
-			Assert.IsTrue(runChiSquaredTests(random), "Chi2 test detected possible non-randomness");
-		}
-
-		private static bool runChiSquaredTests(
-			SecureRandom random)
-		{
-			int passes = 0;
-
-			for (int tries = 0; tries < 100; ++tries)
-			{
-				double chi2 = measureChiSquared(random, 1000);
-				if (chi2 < 285.0) // 255 degrees of freedom in test => Q ~ 10.0% for 285
-					++passes;
-			}
-
-			return passes > 75;
-		}
-
-		private static double measureChiSquared(
-			SecureRandom	random,
-			int				rounds)
-		{
-			int[] counts = new int[256];
-
-			byte[] bs = new byte[256];
-			for (int i = 0; i < rounds; ++i)
-			{
-				random.NextBytes(bs);
-
-				for (int b = 0; b < 256; ++b)
-				{
-					++counts[bs[b]];
-				}
-			}
-
-			byte mask = SecureRandom.GetSeed(1)[0];
-			for (int i = 0; i < rounds; ++i)
-			{
-				random.NextBytes(bs);
-
-				for (int b = 0; b < 256; ++b)
-				{
-					++counts[bs[b] ^ mask];
-				}
-
-				++mask;
-			}
-
-			byte shift = SecureRandom.GetSeed(1)[0];
-			for (int i = 0; i < rounds; ++i)
-			{
-				random.NextBytes(bs);
-
-				for (int b = 0; b < 256; ++b)
-				{
-					++counts[(byte)(bs[b] + shift)];
-				}
-
-				++shift;
-			}
-
-			int total = 3 * rounds;
-
-			double chi2 = 0;
-			for (int k = 0; k < counts.Length; ++k)
-			{
-				double diff = ((double) counts[k]) - total;
-				double diff2 = diff * diff;
-
-				chi2 += diff2;
-			}
-
-			chi2 /= total;
-
-			return chi2;
-		}
-	}
+        [Test]
+        public void TestDefault()
+        {
+            SecureRandom random = new SecureRandom();
+
+            CheckSecureRandom(random);
+        }
+
+        [Test]
+        public void TestSha1Prng()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
+
+            CheckSecureRandom(random);
+        }
+
+        [Test]
+        public void TestSha1PrngBackward()
+        {
+            byte[] seed = Encoding.ASCII.GetBytes("backward compatible");
+
+            SecureRandom sx = new SecureRandom(seed);
+            SecureRandom sy = SecureRandom.GetInstance("SHA1PRNG", false); sy.SetSeed(seed);
+
+            byte[] bx = new byte[128]; sx.NextBytes(bx);
+            byte[] by = new byte[128]; sy.NextBytes(by);
+
+            Assert.IsTrue(Arrays.AreEqual(bx, by));
+        }
+
+        [Test]
+        public void TestSha256Prng()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA256PRNG");
+
+            CheckSecureRandom(random);
+        }
+
+        [Test]
+        public void TestThreadedSeed()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA1PRNG", false);
+            random.SetSeed(new ThreadedSeedGenerator().GenerateSeed(20, false));
+
+            CheckSecureRandom(random);
+        }
+
+        [Test]
+        public void TestVmpcPrng()
+        {
+            SecureRandom random = new SecureRandom(new VmpcRandomGenerator());
+            random.SetSeed(SecureRandom.GetSeed(32));
+
+            CheckSecureRandom(random);
+        }
+
+
+        private static void CheckSecureRandom(SecureRandom random)
+        {
+            // Note: This will periodically (< 1e-6 probability) give a false alarm.
+            // That's randomness for you!
+            Assert.IsTrue(RunChiSquaredTests(random), "Chi2 test detected possible non-randomness");
+        }
+
+        private static bool RunChiSquaredTests(SecureRandom random)
+        {
+            int passes = 0;
+
+            for (int tries = 0; tries < 100; ++tries)
+            {
+                double chi2 = MeasureChiSquared(random, 1000);
+
+                // 255 degrees of freedom in test => Q ~ 10.0% for 285
+                if (chi2 < 285.0)
+                {
+                    ++passes;
+                }
+            }
+
+            return passes > 75;
+        }
+
+        private static double MeasureChiSquared(SecureRandom random, int rounds)
+        {
+            byte[] opts = SecureRandom.GetSeed(2);
+            int[] counts = new int[256];
+
+            byte[] bs = new byte[256];
+            for (int i = 0; i < rounds; ++i)
+            {
+                random.NextBytes(bs);
+
+                for (int b = 0; b < 256; ++b)
+                {
+                    ++counts[bs[b]];
+                }
+            }
+
+            byte mask = opts[0];
+            for (int i = 0; i < rounds; ++i)
+            {
+                random.NextBytes(bs);
+
+                for (int b = 0; b < 256; ++b)
+                {
+                    ++counts[bs[b] ^ mask];
+                }
+
+                ++mask;
+            }
+
+            byte shift = opts[1];
+            for (int i = 0; i < rounds; ++i)
+            {
+                random.NextBytes(bs);
+
+                for (int b = 0; b < 256; ++b)
+                {
+                    ++counts[(byte)(bs[b] + shift)];
+                }
+
+                ++shift;
+            }
+
+            int total = 3 * rounds;
+
+            double chi2 = 0;
+            for (int k = 0; k < counts.Length; ++k)
+            {
+                double diff = ((double) counts[k]) - total;
+                double diff2 = diff * diff;
+
+                chi2 += diff2;
+            }
+
+            chi2 /= total;
+
+            return chi2;
+        }
+    }
 }