summary refs log tree commit diff
diff options
context:
space:
mode:
authorJeffrey Stedfast <jeff@xamarin.com>2015-10-18 11:30:17 -0400
committerJeffrey Stedfast <jeff@xamarin.com>2015-10-18 11:30:17 -0400
commit8d1add6bb7609792163a91404e8cf7fec2040516 (patch)
tree3f8ccb6bb30bdacf29d0e2e9259346b891b7d8df
parentUpdated Visual Studio 2010 project files (diff)
parenthttps://github.com/bcgit/bc-csharp/issues/37 (diff)
downloadBouncyCastle.NET-ed25519-8d1add6bb7609792163a91404e8cf7fec2040516.tar.xz
Merge branch 'master' into vs2010
-rw-r--r--crypto-test/CryptoTest.cs43
-rw-r--r--crypto/crypto.csproj55
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakeParticipant.cs456
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs103
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs108
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs101
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs72
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs51
-rw-r--r--crypto/src/crypto/agreement/jpake/JPakeUtilities.cs390
-rw-r--r--crypto/src/crypto/tls/TlsClientProtocol.cs12
-rw-r--r--crypto/src/crypto/tls/TlsProtocol.cs36
-rw-r--r--crypto/src/crypto/tls/TlsServerProtocol.cs12
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs49
-rw-r--r--crypto/src/openpgp/PgpKeyRingGenerator.cs160
-rw-r--r--crypto/src/openpgp/PgpPbeEncryptedData.cs37
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs346
-rw-r--r--crypto/src/openpgp/PgpUtilities.cs50
-rw-r--r--crypto/test/src/crypto/agreement/test/AllTests.cs31
-rw-r--r--crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs566
-rw-r--r--crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs117
-rw-r--r--crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs306
-rw-r--r--crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs4
-rw-r--r--crypto/test/src/openpgp/examples/ByteArrayHandler.cs2
-rw-r--r--crypto/test/src/openpgp/examples/PbeFileProcessor.cs2
-rw-r--r--crypto/test/src/openpgp/test/PGPPBETest.cs10
-rw-r--r--crypto/test/src/openpgp/test/PGPRSATest.cs2
-rw-r--r--crypto/test/src/openpgp/test/PgpKeyRingTest.cs6
-rw-r--r--crypto/test/src/openpgp/test/PgpUnicodeTest.cs15
28 files changed, 2999 insertions, 143 deletions
diff --git a/crypto-test/CryptoTest.cs b/crypto-test/CryptoTest.cs
index 1f6973e28..5a411b482 100644
--- a/crypto-test/CryptoTest.cs
+++ b/crypto-test/CryptoTest.cs
@@ -14,27 +14,28 @@ namespace crypto_test
 
 			try
 			{
-				Org.BouncyCastle.Asn1.Tests.RegressionTest.Main(args);
-				//Org.BouncyCastle.Bcpg.OpenPgp.Tests.Dsa2Test.?
-				Org.BouncyCastle.Bcpg.OpenPgp.Tests.RegressionTest.Main(args);
-				Org.BouncyCastle.Bcpg.OpenPgp.Examples.Tests.AllTests.Main(args);
-				Org.BouncyCastle.Cms.Tests.AllTests.Main(args);
-				Org.BouncyCastle.Crypto.Tests.RegressionTest.Main(args);
-				Org.BouncyCastle.Crypto.IO.Tests.AllTests.Main(args);
-				Org.BouncyCastle.Math.Tests.AllTests.Main(args);
-				Org.BouncyCastle.Math.EC.Tests.AllTests.Main(args);
-				Org.BouncyCastle.Ocsp.Tests.AllTests.Main(args);
-				//Org.BouncyCastle.Pkcs.Tests.?
-				Org.BouncyCastle.Pkcs.Tests.EncryptedPrivateKeyInfoTest.Main(args);
-				Org.BouncyCastle.Pkcs.Tests.Pkcs10Test.Main(args);
-				Org.BouncyCastle.Pkcs.Tests.Pkcs12StoreTest.Main(args);
-				//Org.BouncyCastle.OpenSsl.Tests.?
-				Org.BouncyCastle.OpenSsl.Tests.ReaderTest.Main(args);
-				Org.BouncyCastle.OpenSsl.Tests.WriterTest.Main(args);
-				//Org.BouncyCastle.Security.Tests.?
-				Org.BouncyCastle.Tests.RegressionTest.Main(args);
-				Org.BouncyCastle.Tsp.Tests.AllTests.Main(args);
-				//Org.BouncyCastle.X509.Tests.?
+                Org.BouncyCastle.Asn1.Tests.RegressionTest.Main(args);
+                //Org.BouncyCastle.Bcpg.OpenPgp.Tests.Dsa2Test.?
+                Org.BouncyCastle.Bcpg.OpenPgp.Tests.RegressionTest.Main(args);
+                Org.BouncyCastle.Bcpg.OpenPgp.Examples.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Cms.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Crypto.Agreement.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Crypto.Tests.RegressionTest.Main(args);
+                Org.BouncyCastle.Crypto.IO.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Math.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Math.EC.Tests.AllTests.Main(args);
+                Org.BouncyCastle.Ocsp.Tests.AllTests.Main(args);
+                //Org.BouncyCastle.Pkcs.Tests.?
+                Org.BouncyCastle.Pkcs.Tests.EncryptedPrivateKeyInfoTest.Main(args);
+                Org.BouncyCastle.Pkcs.Tests.Pkcs10Test.Main(args);
+                Org.BouncyCastle.Pkcs.Tests.Pkcs12StoreTest.Main(args);
+                //Org.BouncyCastle.OpenSsl.Tests.?
+                Org.BouncyCastle.OpenSsl.Tests.ReaderTest.Main(args);
+                Org.BouncyCastle.OpenSsl.Tests.WriterTest.Main(args);
+                //Org.BouncyCastle.Security.Tests.?
+                Org.BouncyCastle.Tests.RegressionTest.Main(args);
+                Org.BouncyCastle.Tsp.Tests.AllTests.Main(args);
+                //Org.BouncyCastle.X509.Tests.?
 			}
 			catch (Exception e)
 			{
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index df7df9f5a..b80a3fec0 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -3199,6 +3199,41 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\agreement\jpake\JPakeParticipant.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakePrimeOrderGroup.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakePrimeOrderGroups.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakeRound1Payload.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakeRound2Payload.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakeRound3Payload.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\agreement\jpake\JPakeUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\agreement\kdf\DHKdfParameters.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -10990,6 +11025,26 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\agreement\test\AllTests.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\crypto\agreement\test\JPakeParticipantTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\crypto\agreement\test\JPakePrimeOrderGroupTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\crypto\agreement\test\JPakeUtilitiesTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\crypto\examples\DESExample.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs
new file mode 100755
index 000000000..794284866
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs
@@ -0,0 +1,456 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
+    ///
+    /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper
+    /// <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf">
+    /// "Password Authenticated Key Exchange by Juggling, 2008."</a>
+    ///
+    /// The J-PAKE protocol is symmetric.
+    /// There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>.
+    /// An instance of JPakeParticipant represents one participant, and
+    /// is the primary interface for executing the exchange.
+    ///
+    /// To execute an exchange, construct a JPakeParticipant on each end,
+    /// and call the following 7 methods
+    /// (once and only once, in the given order, for each participant, sending messages between them as described):
+    ///
+    /// CreateRound1PayloadToSend() - and send the payload to the other participant
+    /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant
+    /// CreateRound2PayloadToSend() - and send the payload to the other participant
+    /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant
+    /// CalculateKeyingMaterial()
+    /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant
+    /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant
+    ///
+    /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial().
+    /// The caller is responsible for deriving the session key using a secure key derivation function (KDF).
+    ///
+    /// Round 3 is an optional key confirmation process.
+    /// If you do not execute round 3, then there is no assurance that both participants are using the same key.
+    /// (i.e. if the participants used different passwords, then their session keys will differ.)
+    ///
+    /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
+    ///
+    /// The symmetric design can easily support the asymmetric cases when one party initiates the communication.
+    /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass.
+    /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload.
+    /// These are the trivial techniques to optimize the communication.
+    ///
+    /// The key confirmation process is implemented as specified in
+    /// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+    /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
+    ///
+    /// This class is stateful and NOT threadsafe.
+    /// Each instance should only be used for ONE complete J-PAKE exchange
+    /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange).
+    /// </summary>
+    public class JPakeParticipant
+    {
+        // Possible internal states.  Used for state checking.
+        public static readonly int STATE_INITIALIZED = 0;
+        public static readonly int STATE_ROUND_1_CREATED = 10;
+        public static readonly int STATE_ROUND_1_VALIDATED = 20;
+        public static readonly int STATE_ROUND_2_CREATED = 30;
+        public static readonly int STATE_ROUND_2_VALIDATED = 40;
+        public static readonly int STATE_KEY_CALCULATED = 50;
+        public static readonly int STATE_ROUND_3_CREATED = 60;
+        public static readonly int STATE_ROUND_3_VALIDATED = 70;
+
+        // Unique identifier of this participant.
+        // The two participants in the exchange must NOT share the same id.
+        private string participantId;
+
+        // Shared secret.  This only contains the secret between construction
+        // and the call to CalculateKeyingMaterial().
+        //
+        // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's,
+        // and the field is set to null.
+        private char[] password;
+
+        // Digest to use during calculations.
+        private IDigest digest;
+        
+        // Source of secure random data.
+        private readonly SecureRandom random;
+
+        private readonly BigInteger p;
+        private readonly BigInteger q;
+        private readonly BigInteger g;
+
+        // The participantId of the other participant in this exchange.
+        private string partnerParticipantId;
+
+        // Alice's x1 or Bob's x3.
+        private BigInteger x1;
+        // Alice's x2 or Bob's x4.
+        private BigInteger x2;
+        // Alice's g^x1 or Bob's g^x3.
+        private BigInteger gx1;
+        // Alice's g^x2 or Bob's g^x4.
+        private BigInteger gx2;
+        // Alice's g^x3 or Bob's g^x1.
+        private BigInteger gx3;
+        // Alice's g^x4 or Bob's g^x2.
+        private BigInteger gx4;
+        // Alice's B or Bob's A.
+        private BigInteger b;
+
+        // The current state.
+        // See the <tt>STATE_*</tt> constants for possible values.
+        private int state;
+
+        /// <summary>
+        /// Convenience constructor for a new JPakeParticipant that uses
+        /// the JPakePrimeOrderGroups#NIST_3072 prime order group,
+        /// a SHA-256 digest, and a default SecureRandom implementation.
+        ///
+        /// After construction, the State state will be STATE_INITIALIZED.
+        /// 
+        /// Throws NullReferenceException if any argument is null. Throws
+        /// ArgumentException if password is empty.
+        /// </summary>
+        /// <param name="participantId">Unique identifier of this participant.
+        ///      The two participants in the exchange must NOT share the same id.</param>
+        /// <param name="password">Shared secret.
+        ///      A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
+        ///      Caller should clear the input password as soon as possible.</param>
+        public JPakeParticipant(string participantId, char[] password)
+            : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { }
+
+        /// <summary>
+        /// Convenience constructor for a new JPakeParticipant that uses
+        /// a SHA-256 digest, and a default SecureRandom implementation.
+        ///
+        /// After construction, the State state will be STATE_INITIALIZED.
+        /// 
+        /// Throws NullReferenceException if any argument is null. Throws
+        /// ArgumentException if password is empty.
+        /// </summary>
+        /// <param name="participantId">Unique identifier of this participant.
+        ///      The two participants in the exchange must NOT share the same id.</param>
+        /// <param name="password">Shared secret.
+        ///      A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
+        ///      Caller should clear the input password as soon as possible.</param>
+        /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
+        public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group)
+            : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { }
+
+
+        /// <summary>
+        /// Constructor for a new JPakeParticipant.
+        ///
+        /// After construction, the State state will be STATE_INITIALIZED.
+        /// 
+        /// Throws NullReferenceException if any argument is null. Throws
+        /// ArgumentException if password is empty.
+        /// </summary>
+        /// <param name="participantId">Unique identifier of this participant.
+        ///      The two participants in the exchange must NOT share the same id.</param>
+        /// <param name="password">Shared secret.
+        ///      A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
+        ///      Caller should clear the input password as soon as possible.</param>
+        /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
+        /// <param name="digest">Digest to use during zero knowledge proofs and key confirmation
+        ///     (SHA-256 or stronger preferred).</param>
+        /// <param name="random">Source of secure random data for x1 and x2, and for the zero knowledge proofs.</param>
+        public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random)
+        {
+            JPakeUtilities.ValidateNotNull(participantId, "participantId");
+            JPakeUtilities.ValidateNotNull(password, "password");
+            JPakeUtilities.ValidateNotNull(group, "p");
+            JPakeUtilities.ValidateNotNull(digest, "digest");
+            JPakeUtilities.ValidateNotNull(random, "random");
+
+            if (password.Length == 0)
+            {
+                throw new ArgumentException("Password must not be empty.");
+            }
+
+            this.participantId = participantId;
+
+            // Create a defensive copy so as to fully encapsulate the password.
+            // 
+            // This array will contain the password for the lifetime of this
+            // participant BEFORE CalculateKeyingMaterial() is called.
+            // 
+            // i.e. When CalculateKeyingMaterial() is called, the array will be cleared
+            // in order to remove the password from memory.
+            // 
+            // The caller is responsible for clearing the original password array
+            // given as input to this constructor.
+            this.password = new char[password.Length];
+            Array.Copy(password, this.password, password.Length);
+
+            this.p = group.P;
+            this.q = group.Q;
+            this.g = group.G;
+
+            this.digest = digest;
+            this.random = random;
+
+            this.state = STATE_INITIALIZED;
+        }
+
+        /// <summary>
+        /// Gets the current state of this participant.
+        /// See the <tt>STATE_*</tt> constants for possible values.
+        /// </summary>
+        public virtual int State
+        {
+            get { return state; }
+        }
+
+
+        /// <summary>
+        /// Creates and returns the payload to send to the other participant during round 1.
+        ///
+        /// After execution, the State state} will be STATE_ROUND_1_CREATED}.
+        /// </summary>
+        public virtual JPakeRound1Payload CreateRound1PayloadToSend()
+        {
+            if (this.state >= STATE_ROUND_1_CREATED)
+                throw new InvalidOperationException("Round 1 payload already created for " + this.participantId);
+
+            this.x1 = JPakeUtilities.GenerateX1(q, random);
+            this.x2 = JPakeUtilities.GenerateX2(q, random);
+
+            this.gx1 = JPakeUtilities.CalculateGx(p, g, x1);
+            this.gx2 = JPakeUtilities.CalculateGx(p, g, x2);
+            BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
+            BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
+
+            this.state = STATE_ROUND_1_CREATED;
+
+            return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
+        }
+
+        /// <summary>
+        /// Validates the payload received from the other participant during round 1.
+        ///
+        /// Must be called prior to CreateRound2PayloadToSend().
+        ///
+        /// After execution, the State state will be  STATE_ROUND_1_VALIDATED.
+        /// 
+        /// Throws CryptoException if validation fails. Throws InvalidOperationException
+        /// if called multiple times.
+        /// </summary>
+        public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived)
+        {
+            if (this.state >= STATE_ROUND_1_VALIDATED)
+                throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId);
+
+            this.partnerParticipantId = round1PayloadReceived.ParticipantId;
+            this.gx3 = round1PayloadReceived.Gx1;
+            this.gx4 = round1PayloadReceived.Gx2;
+
+            BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1;
+            BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2;
+
+            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId);
+            JPakeUtilities.ValidateGx4(gx4);
+            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest);
+            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); 
+            this.state = STATE_ROUND_1_VALIDATED;
+        }
+
+        /// <summary>
+        /// Creates and returns the payload to send to the other participant during round 2.
+        ///
+        /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method.
+        ///
+        /// After execution, the State state will be  STATE_ROUND_2_CREATED.
+        ///
+        /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
+        /// </summary>
+        public virtual JPakeRound2Payload CreateRound2PayloadToSend()
+        {
+            if (this.state >= STATE_ROUND_2_CREATED)
+                throw new InvalidOperationException("Round 2 payload already created for " + this.participantId);
+            if (this.state < STATE_ROUND_1_VALIDATED)
+                throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId);
+
+            BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4);
+            BigInteger s = JPakeUtilities.CalculateS(password);
+            BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s);
+            BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s);
+            BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
+
+            this.state = STATE_ROUND_2_CREATED;
+
+            return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s);
+        }
+
+        /// <summary>
+        /// Validates the payload received from the other participant during round 2.
+        /// Note that this DOES NOT detect a non-common password.
+        /// The only indication of a non-common password is through derivation
+        /// of different keys (which can be detected explicitly by executing round 3 and round 4)
+        ///
+        /// Must be called prior to CalculateKeyingMaterial().
+        ///
+        /// After execution, the State state will be STATE_ROUND_2_VALIDATED.
+        ///
+        /// Throws CryptoException if validation fails. Throws
+        /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
+        /// </summary>
+        public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived)
+        {
+            if (this.state >= STATE_ROUND_2_VALIDATED)
+                throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId);
+            if (this.state < STATE_ROUND_1_VALIDATED)
+                throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId);
+
+            BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2);
+            this.b = round2PayloadReceived.A;
+            BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s;
+
+            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId);
+            JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId);
+            JPakeUtilities.ValidateGa(gB);
+            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest);
+
+            this.state = STATE_ROUND_2_VALIDATED;
+        }
+
+        /// <summary>
+        /// Calculates and returns the key material.
+        /// A session key must be derived from this key material using a secure key derivation function (KDF).
+        /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant).
+        ///
+        /// The keying material will be identical for each participant if and only if
+        /// each participant's password is the same.  i.e. If the participants do not
+        /// share the same password, then each participant will derive a different key.
+        /// Therefore, if you immediately start using a key derived from
+        /// the keying material, then you must handle detection of incorrect keys.
+        /// If you want to handle this detection explicitly, you can optionally perform
+        /// rounds 3 and 4.  See JPakeParticipant for details on how to execute
+        /// rounds 3 and 4.
+        ///
+        /// The keying material will be in the range <tt>[0, p-1]</tt>.
+        ///
+        /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method.
+        /// 
+        /// As a side effect, the internal password array is cleared, since it is no longer needed.
+        ///
+        /// After execution, the State state will be STATE_KEY_CALCULATED.
+        ///
+        /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload),
+        /// or if called multiple times.
+        /// </summary>
+        public virtual BigInteger CalculateKeyingMaterial()
+        {
+            if (this.state >= STATE_KEY_CALCULATED)
+                throw new InvalidOperationException("Key already calculated for " + participantId);
+            if (this.state < STATE_ROUND_2_VALIDATED)
+                throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId);
+
+            BigInteger s = JPakeUtilities.CalculateS(password);
+
+            // Clear the password array from memory, since we don't need it anymore.
+            // Also set the field to null as a flag to indicate that the key has already been calculated.
+            Array.Clear(password, 0, password.Length);
+            this.password = null;
+
+            BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b);
+
+            // Clear the ephemeral private key fields as well.
+            // Note that we're relying on the garbage collector to do its job to clean these up.
+            // The old objects will hang around in memory until the garbage collector destroys them.
+            // 
+            // If the ephemeral private keys x1 and x2 are leaked,
+            // the attacker might be able to brute-force the password.
+            this.x1 = null;
+            this.x2 = null;
+            this.b = null;
+
+            // Do not clear gx* yet, since those are needed by round 3.
+
+            this.state = STATE_KEY_CALCULATED;
+
+            return keyingMaterial;
+        }
+
+        /// <summary>
+        /// Creates and returns the payload to send to the other participant during round 3.
+        ///
+        /// See JPakeParticipant for more details on round 3.
+        ///
+        /// After execution, the State state} will be  STATE_ROUND_3_CREATED.
+        /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple
+        /// times.
+        /// </summary>
+        /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param> 
+        public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial)
+        {
+            if (this.state >= STATE_ROUND_3_CREATED)
+                throw new InvalidOperationException("Round 3 payload already created for " + this.participantId);
+            if (this.state < STATE_KEY_CALCULATED)
+                throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId);
+
+            BigInteger macTag = JPakeUtilities.CalculateMacTag(
+                this.participantId,
+                this.partnerParticipantId,
+                this.gx1,
+                this.gx2,
+                this.gx3,
+                this.gx4,
+                keyingMaterial,
+                this.digest);
+
+            this.state = STATE_ROUND_3_CREATED;
+
+            return new JPakeRound3Payload(participantId, macTag);
+        }
+
+        /// <summary>
+        /// Validates the payload received from the other participant during round 3.
+        ///
+        /// See JPakeParticipant for more details on round 3.
+        ///
+        /// After execution, the State state will be STATE_ROUND_3_VALIDATED.
+        /// 
+        /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to
+        /// CalculateKeyingMaterial or multiple times
+        /// </summary>
+        /// <param name="round3PayloadReceived">The round 3 payload received from the other participant.</param> 
+        /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param> 
+        public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial)
+        {
+            if (this.state >= STATE_ROUND_3_VALIDATED)
+                throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId);
+            if (this.state < STATE_KEY_CALCULATED)
+                throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId);
+
+            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId);
+            JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId);
+
+            JPakeUtilities.ValidateMacTag(
+                this.participantId,
+                this.partnerParticipantId,
+                this.gx1,
+                this.gx2,
+                this.gx3,
+                this.gx4,
+                keyingMaterial,
+                this.digest,
+                round3PayloadReceived.MacTag);
+
+            // Clear the rest of the fields.
+            this.gx1 = null;
+            this.gx2 = null;
+            this.gx3 = null;
+            this.gx4 = null;
+
+            this.state = STATE_ROUND_3_VALIDATED;
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs
new file mode 100755
index 000000000..08ffe1a55
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs
@@ -0,0 +1,103 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// A pre-computed prime order group for use during a J-PAKE exchange.
+    ///
+    /// Typically a Schnorr group is used.  In general, J-PAKE can use any prime order group
+    /// that is suitable for public key cryptography, including elliptic curve cryptography.
+    ///
+    /// See JPakePrimeOrderGroups for convenient standard groups.
+    ///
+    /// NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
+    /// many groups that can be used for the desired level of security.
+    /// </summary>
+    public class JPakePrimeOrderGroup
+    {
+        private readonly BigInteger p;
+        private readonly BigInteger q;
+        private readonly BigInteger g;
+
+        /// <summary>
+        /// Constructs a new JPakePrimeOrderGroup.
+        ///
+        /// In general, you should use one of the pre-approved groups from
+        /// JPakePrimeOrderGroups, rather than manually constructing one.
+        ///
+        /// The following basic checks are performed:
+        ///
+        /// p-1 must be evenly divisible by q
+        /// g must be in [2, p-1]
+        /// g^q mod p must equal 1
+        /// p must be prime (within reasonably certainty)
+        /// q must be prime (within reasonably certainty)
+        ///
+        /// The prime checks are performed using BigInteger#isProbablePrime(int),
+        /// and are therefore subject to the same probability guarantees.
+        ///
+        /// These checks prevent trivial mistakes.
+        /// However, due to the small uncertainties if p and q are not prime,
+        /// advanced attacks are not prevented.
+        /// Use it at your own risk.
+        /// 
+        /// Throws NullReferenceException if any argument is null. Throws
+        /// InvalidOperationException is any of the above validations fail.
+        /// </summary>
+        public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g)
+            : this(p, q, g, false)
+        {
+             // Don't skip the checks on user-specified groups.
+        }
+
+        /// <summary>
+        /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups.
+        /// These pre-approved groups can avoid the expensive checks.
+        /// User-specified groups should not use this constructor.
+        /// </summary>
+        public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks)
+        {
+            JPakeUtilities.ValidateNotNull(p, "p");
+            JPakeUtilities.ValidateNotNull(q, "q");
+            JPakeUtilities.ValidateNotNull(g, "g");
+
+            if (!skipChecks)
+            {
+                if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero))
+                    throw new ArgumentException("p-1 must be evenly divisible by q");
+                if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1)
+                    throw new ArgumentException("g must be in [2, p-1]");
+                if (!g.ModPow(q, p).Equals(JPakeUtilities.One))
+                    throw new ArgumentException("g^q mod p must equal 1");
+
+                // Note these checks do not guarantee that p and q are prime.
+                // We just have reasonable certainty that they are prime.
+                if (!p.IsProbablePrime(20))
+                    throw new ArgumentException("p must be prime");
+                if (!q.IsProbablePrime(20))
+                    throw new ArgumentException("q must be prime");
+            }
+
+            this.p = p;
+            this.q = q;
+            this.g = g;
+        }
+
+        public virtual BigInteger P
+        {
+            get { return p; }
+        }
+
+        public virtual BigInteger Q
+        {
+            get { return q; }
+        }
+
+        public virtual BigInteger G
+        {
+            get { return g; }
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs
new file mode 100755
index 000000000..192cd2b51
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs
@@ -0,0 +1,108 @@
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// Standard pre-computed prime order groups for use by J-PAKE.
+    /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.)
+    /// <p/>
+    /// This class contains some convenient constants for use as input for
+    /// constructing {@link JPAKEParticipant}s.
+    /// <p/>
+    /// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB),
+    /// and from the prime order groups
+    /// <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">published by NIST</a>.
+    /// </summary>
+    public class JPakePrimeOrderGroups
+    {
+        /// <summary>
+        /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB)
+        /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security.
+        /// </summary>
+        public static readonly JPakePrimeOrderGroup SUN_JCE_1024 = new JPakePrimeOrderGroup(
+            // p
+            new BigInteger(
+                "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" +
+                    "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" +
+                    "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" +
+                    "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16),
+            // q
+            new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16),
+            // g
+            new BigInteger(
+                "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" +
+                    "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" +
+                    "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" +
+                    "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16),
+            true
+        );
+
+        /// <summary>
+        /// From NIST.
+        /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security.
+        /// </summary>
+        public static readonly JPakePrimeOrderGroup NIST_2048 = new JPakePrimeOrderGroup(
+            // p
+            new BigInteger(
+                "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" +
+                    "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" +
+                    "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" +
+                    "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" +
+                    "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" +
+                    "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" +
+                    "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" +
+                    "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16),
+            // q
+            new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16),
+            // g
+            new BigInteger(
+                "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" +
+                    "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" +
+                    "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" +
+                    "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" +
+                    "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" +
+                    "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" +
+                    "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" +
+                    "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16),
+            true
+        );
+
+        /// <summary>
+        /// From NIST.
+        /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security.
+        /// </summary>
+        public static readonly JPakePrimeOrderGroup NIST_3072 = new JPakePrimeOrderGroup(
+            // p
+            new BigInteger(
+                "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" +
+                    "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" +
+                    "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" +
+                    "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" +
+                    "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" +
+                    "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" +
+                    "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" +
+                    "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" +
+                    "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" +
+                    "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" +
+                    "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" +
+                    "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16),
+            // q
+            new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16),
+            // g
+            new BigInteger(
+                "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" +
+                    "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" +
+                    "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" +
+                    "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" +
+                    "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" +
+                    "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" +
+                    "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" +
+                    "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" +
+                    "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" +
+                    "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" +
+                    "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" +
+                    "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16),
+            true
+        );
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs
new file mode 100755
index 000000000..9e4ab7a5f
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs
@@ -0,0 +1,101 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// The payload sent/received during the first round of a J-PAKE exchange.
+    /// 
+    /// Each JPAKEParticipant creates and sends an instance of this payload to
+    /// the other. The payload to send should be created via 
+    /// JPAKEParticipant.CreateRound1PayloadToSend().
+    /// 
+    /// Each participant must also validate the payload received from the other.
+    /// The received payload should be validated via 
+    /// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload).
+    /// </summary>
+    public class JPakeRound1Payload
+    {
+        /// <summary>
+        /// The id of the JPAKEParticipant who created/sent this payload.
+        /// </summary>
+        private readonly string participantId;
+
+        /// <summary>
+        /// The value of g^x1
+        /// </summary>
+        private readonly BigInteger gx1;
+
+        /// <summary>
+        /// The value of g^x2
+        /// </summary>
+        private readonly BigInteger gx2;
+
+        /// <summary>
+        /// The zero knowledge proof for x1.
+        /// 
+        /// This is a two element array, containing {g^v, r} for x1.
+        /// </summary>
+        private readonly BigInteger[] knowledgeProofForX1;
+
+        /// <summary>
+        /// The zero knowledge proof for x2.
+        /// 
+        /// This is a two element array, containing {g^v, r} for x2.
+        /// </summary>
+        private readonly BigInteger[] knowledgeProofForX2;
+
+        public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2)
+        {
+            JPakeUtilities.ValidateNotNull(participantId, "participantId");
+            JPakeUtilities.ValidateNotNull(gx1, "gx1");
+            JPakeUtilities.ValidateNotNull(gx2, "gx2");
+            JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1");
+            JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2");
+
+            this.participantId = participantId;
+            this.gx1 = gx1;
+            this.gx2 = gx2;
+            this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length];
+            Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length);
+            this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length];
+            Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length);
+        }
+
+        public virtual string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public virtual BigInteger Gx1
+        {
+            get { return gx1; }
+        }
+
+        public virtual BigInteger Gx2
+        {
+            get { return gx2; }
+        }
+
+        public virtual BigInteger[] KnowledgeProofForX1
+        {
+            get
+            {
+                BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length];
+                Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length);
+                return kp;
+            }
+        }
+
+        public virtual BigInteger[] KnowledgeProofForX2
+        {
+            get
+            {
+                BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length];
+                Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length);
+                return kp;
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs
new file mode 100755
index 000000000..47962cb3f
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs
@@ -0,0 +1,72 @@
+using System;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// The payload sent/received during the second round of a J-PAKE exchange.
+    /// 
+    /// Each JPAKEParticipant creates and sends an instance
+    /// of this payload to the other JPAKEParticipant.
+    /// The payload to send should be created via
+    /// JPAKEParticipant#createRound2PayloadToSend()
+    /// 
+    /// Each JPAKEParticipant must also validate the payload
+    /// received from the other JPAKEParticipant.
+    /// The received payload should be validated via
+    /// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload)
+    /// </summary>
+    public class JPakeRound2Payload
+    {
+        /// <summary>
+        /// The id of the JPAKEParticipant who created/sent this payload.
+        /// </summary>
+        private readonly string participantId;
+
+        /// <summary>
+        /// The value of A, as computed during round 2.
+        /// </summary>
+        private readonly BigInteger a;
+
+        /// <summary>
+        /// The zero knowledge proof for x2 * s.
+        /// 
+        /// This is a two element array, containing {g^v, r} for x2 * s.
+        /// </summary>
+        private readonly BigInteger[] knowledgeProofForX2s;
+
+        public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s)
+        {
+            JPakeUtilities.ValidateNotNull(participantId, "participantId");
+            JPakeUtilities.ValidateNotNull(a, "a");
+            JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s");
+
+            this.participantId = participantId;
+            this.a = a;
+            this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length];
+            knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0);
+        }
+
+        public virtual string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public virtual BigInteger A
+        {
+            get { return a; }
+        }
+
+        public virtual BigInteger[] KnowledgeProofForX2s
+        {
+            get
+            {
+                BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length];
+                Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length);
+                return kp;
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs
new file mode 100755
index 000000000..767702f23
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs
@@ -0,0 +1,51 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// The payload sent/received during the optional third round of a J-PAKE exchange,
+    /// which is for explicit key confirmation.
+    ///
+    /// Each JPAKEParticipant creates and sends an instance
+    /// of this payload to the other JPAKEParticipant.
+    /// The payload to send should be created via
+    /// JPAKEParticipant#createRound3PayloadToSend(BigInteger)
+    ///
+    /// Eeach JPAKEParticipant must also validate the payload
+    /// received from the other JPAKEParticipant.
+    /// The received payload should be validated via
+    /// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger)
+    /// </summary>
+    public class JPakeRound3Payload
+    {
+        /// <summary>
+        /// The id of the {@link JPAKEParticipant} who created/sent this payload.
+        /// </summary>
+        private readonly string participantId;
+
+        /// <summary>
+        /// The value of MacTag, as computed by round 3.
+        /// 
+        /// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest)
+        /// </summary>
+        private readonly BigInteger macTag;
+
+        public JPakeRound3Payload(string participantId, BigInteger magTag)
+        {
+            this.participantId = participantId;
+            this.macTag = magTag;
+        }
+
+        public virtual string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public virtual BigInteger MacTag
+        {
+            get { return macTag; }
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs
new file mode 100644
index 000000000..b23518a8c
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Text;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Agreement.JPake
+{
+    /// <summary>
+    /// Primitives needed for a J-PAKE exchange.
+    /// 
+    /// The recommended way to perform a J-PAKE exchange is by using
+    /// two JPAKEParticipants.  Internally, those participants
+    /// call these primitive operations in JPakeUtilities.
+    /// 
+    /// The primitives, however, can be used without a JPAKEParticipant if needed.
+    /// </summary>
+    public abstract class JPakeUtilities
+    {
+        public static readonly BigInteger Zero = BigInteger.Zero;
+        public static readonly BigInteger One = BigInteger.One;
+
+        /// <summary>
+        /// Return a value that can be used as x1 or x3 during round 1.
+        /// The returned value is a random value in the range [0, q-1].
+        /// </summary>
+        public static BigInteger GenerateX1(BigInteger q, SecureRandom random)
+        {
+            BigInteger min = Zero;
+            BigInteger max = q.Subtract(One);
+            return BigIntegers.CreateRandomInRange(min, max, random);
+        }
+
+        /// <summary>
+        /// Return a value that can be used as x2 or x4 during round 1.
+        /// The returned value is a random value in the range [1, q-1].
+        /// </summary>
+        public static BigInteger GenerateX2(BigInteger q, SecureRandom random)
+        {
+            BigInteger min = One;
+            BigInteger max = q.Subtract(One);
+            return BigIntegers.CreateRandomInRange(min, max, random);
+        }
+
+        /// <summary>
+        /// Converts the given password to a BigInteger
+        /// for use in arithmetic calculations.
+        /// </summary>
+        public static BigInteger CalculateS(char[] password)
+        {
+            return new BigInteger(Encoding.UTF8.GetBytes(password));
+        }
+
+        /// <summary>
+        /// Calculate g^x mod p as done in round 1.
+        /// </summary>
+        public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x)
+        {
+            return g.ModPow(x, p);
+        }
+
+        /// <summary>
+        /// Calculate ga as done in round 2.
+        /// </summary>
+        public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4)
+        {
+            // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 
+            return gx1.Multiply(gx3).Multiply(gx4).Mod(p);
+        }
+
+        /// <summary>
+        /// Calculate x2 * s as done in round 2.
+        /// </summary>
+        public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s)
+        {
+            return x2.Multiply(s).Mod(q);
+        }
+
+        /// <summary>
+        /// Calculate A as done in round 2. 
+        /// </summary>
+        public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s)
+        {
+            // A = ga^(x*s)
+            return gA.ModPow(x2s, p);
+        }
+
+        /// <summary>
+        /// Calculate a zero knowledge proof of x using Schnorr's signature.
+        /// The returned array has two elements {g^v, r = v-x*h} for x.
+        /// </summary>
+        public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
+            BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random)
+        {
+            /* Generate a random v, and compute g^v */
+            BigInteger vMin = Zero;
+            BigInteger vMax = q.Subtract(One);
+            BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random);
+
+            BigInteger gv = g.ModPow(v, p);
+            BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h
+
+            return new BigInteger[]
+            {
+                gv,
+                v.Subtract(x.Multiply(h)).Mod(q) // r = v-x*h
+            };
+        }
+
+        private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx,
+            string participantId, IDigest digest)
+        {
+            digest.Reset();
+
+            UpdateDigestIncludingSize(digest, g);
+
+            UpdateDigestIncludingSize(digest, gr);
+
+            UpdateDigestIncludingSize(digest, gx);
+
+            UpdateDigestIncludingSize(digest, participantId);
+
+            byte[] output = DigestUtilities.DoFinal(digest);
+
+            return new BigInteger(output);
+        }
+
+        /// <summary>
+        /// Validates that g^x4 is not 1.
+        /// throws CryptoException if g^x4 is 1
+        /// </summary>
+        public static void ValidateGx4(BigInteger gx4)
+        {
+            if (gx4.Equals(One))
+                throw new CryptoException("g^x validation failed.  g^x should not be 1.");
+        }
+
+        /// <summary>
+        /// Validates that ga is not 1.
+        /// 
+        /// As described by Feng Hao...
+        /// Alice could simply check ga != 1 to ensure it is a generator.
+        /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks.
+        /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q.
+        /// 
+        /// throws CryptoException if ga is 1
+        /// </summary>
+        public static void ValidateGa(BigInteger ga)
+        {
+            if (ga.Equals(One))
+                throw new CryptoException("ga is equal to 1.  It should not be.  The chances of this happening are on the order of 2^160 for a 160-bit q.  Try again.");
+        }
+
+        /// <summary>
+        /// Validates the zero knowledge proof (generated by
+        /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom)
+        /// is correct.
+        /// 
+        /// throws CryptoException if the zero knowledge proof is not correct
+        /// </summary>
+        public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
+            BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest)
+        {
+            /* sig={g^v,r} */
+            BigInteger gv = zeroKnowledgeProof[0];
+            BigInteger r = zeroKnowledgeProof[1];
+
+            BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest);
+            if (!(gx.CompareTo(Zero) == 1 && // g^x > 0
+                gx.CompareTo(p) == -1 && // g^x < p
+                gx.ModPow(q, p).CompareTo(One) == 0 && // g^x^q mod q = 1
+                /*
+                 * Below, I took a straightforward way to compute g^r * g^x^h,
+                 * which needs 2 exp. Using a simultaneous computation technique
+                 * would only need 1 exp.
+                 */
+                g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h
+            {
+                throw new CryptoException("Zero-knowledge proof validation failed");
+            }
+        }
+
+        /// <summary>
+        /// Calculates the keying material, which can be done after round 2 has completed.
+        /// A session key must be derived from this key material using a secure key derivation function (KDF).
+        /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant).
+        /// 
+        /// KeyingMaterial = (B/g^{x2*x4*s})^x2
+        /// </summary>
+        public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q, 
+            BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B)
+        {
+            return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p);
+        }
+
+        /// <summary>
+        /// Validates that the given participant ids are not equal.
+        /// (For the J-PAKE exchange, each participant must use a unique id.)
+        ///
+        /// Throws CryptoException if the participantId strings are equal.
+        /// </summary>
+        public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2)
+        {
+            if (participantId1.Equals(participantId2))
+            {
+                throw new CryptoException(
+                    "Both participants are using the same participantId ("
+                        + participantId1
+                        + "). This is not allowed. "
+                        + "Each participant must use a unique participantId.");
+            }
+        }
+
+        /// <summary>
+        /// Validates that the given participant ids are equal.
+        /// This is used to ensure that the payloads received from
+        /// each round all come from the same participant.
+        /// </summary>
+        public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId)
+        {
+            if (!expectedParticipantId.Equals(actualParticipantId))
+            {
+                throw new CryptoException(
+                    "Received payload from incorrect partner ("
+                        + actualParticipantId
+                        + "). Expected to receive payload from "
+                        + expectedParticipantId
+                        + ".");
+            }
+        }
+
+        /// <summary>
+        /// Validates that the given object is not null.
+        /// throws NullReferenceException if the object is null.
+        /// </summary>
+        /// <param name="obj">object in question</param>
+        /// <param name="description">name of the object (to be used in exception message)</param>
+        public static void ValidateNotNull(object obj, string description)
+        {
+            if (obj == null)
+                throw new ArgumentNullException(description);
+        }
+
+        /// <summary>
+        /// Calculates the MacTag (to be used for key confirmation), as defined by
+        /// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+        /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
+        ///
+        /// MacTag = HMAC(MacKey, MacLen, MacData)
+        /// MacKey = H(K || "JPAKE_KC")
+        /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
+        ///
+        /// Note that both participants use "KC_1_U" because the sender of the round 3 message
+        /// is always the initiator for key confirmation.
+        ///
+        /// HMAC = {@link HMac} used with the given {@link Digest}
+        /// H = The given {@link Digest}
+        /// MacLen = length of MacTag
+        /// </summary>
+        public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId,
+            BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest)
+        {
+            byte[] macKey = CalculateMacKey(keyingMaterial, digest);
+
+            HMac mac = new HMac(digest);
+            mac.Init(new KeyParameter(macKey));
+            Arrays.Fill(macKey, (byte)0);
+
+            /*
+             * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4.
+             */
+            UpdateMac(mac, "KC_1_U");
+            UpdateMac(mac, participantId);
+            UpdateMac(mac, partnerParticipantId);
+            UpdateMac(mac, gx1);
+            UpdateMac(mac, gx2);
+            UpdateMac(mac, gx3);
+            UpdateMac(mac, gx4);
+
+            byte[] macOutput = MacUtilities.DoFinal(mac);
+
+            return new BigInteger(macOutput);
+        }
+
+        /// <summary>
+        /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation).
+        /// 
+        /// MacKey = H(K || "JPAKE_KC")
+        /// </summary>
+        private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest)
+        {
+            digest.Reset();
+
+            UpdateDigest(digest, keyingMaterial);
+            /*
+             * This constant is used to ensure that the macKey is NOT the same as the derived key.
+             */
+            UpdateDigest(digest, "JPAKE_KC");
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        /// <summary>
+        /// Validates the MacTag received from the partner participant.
+        /// 
+        /// throws CryptoException if the participantId strings are equal.
+        /// </summary>
+        public static void ValidateMacTag(string participantId, string partnerParticipantId,
+            BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4,
+            BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag)
+        {
+            /*
+             * Calculate the expected MacTag using the parameters as the partner
+             * would have used when the partner called calculateMacTag.
+             * 
+             * i.e. basically all the parameters are reversed.
+             * participantId <-> partnerParticipantId
+             *            x1 <-> x3
+             *            x2 <-> x4
+             */
+            BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest);
+
+            if (!expectedMacTag.Equals(partnerMacTag))
+            {
+                throw new CryptoException(
+                    "Partner MacTag validation failed. "
+                        + "Therefore, the password, MAC, or digest algorithm of each participant does not match.");
+            }
+        }
+
+        private static void UpdateDigest(IDigest digest, BigInteger bigInteger)
+        {
+            UpdateDigest(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
+        }
+
+        private static void UpdateDigest(IDigest digest, string str)
+        {
+            UpdateDigest(digest, Encoding.UTF8.GetBytes(str));
+        }
+
+        private static void UpdateDigest(IDigest digest, byte[] bytes)
+        {
+            digest.BlockUpdate(bytes, 0, bytes.Length);
+            Arrays.Fill(bytes, (byte)0);
+        }
+
+        private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger)
+        {
+            UpdateDigestIncludingSize(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
+        }
+
+        private static void UpdateDigestIncludingSize(IDigest digest, string str)
+        {
+            UpdateDigestIncludingSize(digest, Encoding.UTF8.GetBytes(str));
+        }
+
+        private static void UpdateDigestIncludingSize(IDigest digest, byte[] bytes)
+        {
+            digest.BlockUpdate(IntToByteArray(bytes.Length), 0, 4);
+            digest.BlockUpdate(bytes, 0, bytes.Length);
+            Arrays.Fill(bytes, (byte)0);
+        }
+
+        private static void UpdateMac(IMac mac, BigInteger bigInteger)
+        {
+            UpdateMac(mac, BigIntegers.AsUnsignedByteArray(bigInteger));
+        }
+
+        private static void UpdateMac(IMac mac, string str)
+        {
+            UpdateMac(mac, Encoding.UTF8.GetBytes(str));
+        }
+
+        private static void UpdateMac(IMac mac, byte[] bytes)
+        {
+            mac.BlockUpdate(bytes, 0, bytes.Length);
+            Arrays.Fill(bytes, (byte)0);
+        }
+
+        private static byte[] IntToByteArray(int value)
+        {
+            return Pack.UInt32_To_BE((uint)value);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs
index 14c1cf4a4..c2447e434 100644
--- a/crypto/src/crypto/tls/TlsClientProtocol.cs
+++ b/crypto/src/crypto/tls/TlsClientProtocol.cs
@@ -43,12 +43,12 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Constructor for non-blocking mode.<br>
-         * <br>
+         * Constructor for non-blocking mode.<br/>
+         * <br/>
          * When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to
          * provide the received ciphertext, then use
-         * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.<br>
-         * <br>
+         * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.<br/>
+         * <br/>
          * Similarly, when data needs to be sent, use
          * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use
          * {@link #readOutput(byte[], int, int)} to get the corresponding
@@ -63,8 +63,8 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Initiates a TLS handshake in the role of client.<br>
-         * <br>
+         * Initiates a TLS handshake in the role of client.<br/>
+         * <br/>
          * In blocking mode, this will not return until the handshake is complete.
          * In non-blocking mode, use {@link TlsPeer#NotifyHandshakeComplete()} to
          * receive a callback when the handshake is complete.
diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs
index 7acc34d3c..fffde0b2b 100644
--- a/crypto/src/crypto/tls/TlsProtocol.cs
+++ b/crypto/src/crypto/tls/TlsProtocol.cs
@@ -604,15 +604,15 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Offer input from an arbitrary source. Only allowed in non-blocking mode.<br>
-         * <br>
+         * Offer input from an arbitrary source. Only allowed in non-blocking mode.<br/>
+         * <br/>
          * After this method returns, the input buffer is "owned" by this object. Other code
-         * must not attempt to do anything with it.<br>
-         * <br>
+         * must not attempt to do anything with it.<br/>
+         * <br/>
          * This method will decrypt and process all records that are fully available.
          * If only part of a record is available, the buffer will be retained until the
-         * remainder of the record is offered.<br>
-         * <br>
+         * remainder of the record is offered.<br/>
+         * <br/>
          * If any records containing application data were processed, the decrypted data
          * can be obtained using {@link #readInput(byte[], int, int)}. If any records
          * containing protocol data were processed, a response may have been generated.
@@ -649,8 +649,8 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         /**
          * Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)}
-         * is guaranteed to be able to return at least this much data.<br>
-         * <br>
+         * is guaranteed to be able to return at least this much data.<br/>
+         * <br/>
          * Only allowed in non-blocking mode.
          * @return The number of bytes of available application data
          */
@@ -666,8 +666,8 @@ namespace Org.BouncyCastle.Crypto.Tls
          * Retrieves received application data. Use {@link #getAvailableInputBytes()} to check
          * how much application data is currently available. This method functions similarly to
          * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data
-         * is available, nothing will be copied and zero will be returned.<br>
-         * <br>
+         * is available, nothing will be copied and zero will be returned.<br/>
+         * <br/>
          * Only allowed in non-blocking mode.
          * @param buffer The buffer to hold the application data
          * @param offset The start offset in the buffer at which the data is written
@@ -684,12 +684,12 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Offer output from an arbitrary source. Only allowed in non-blocking mode.<br>
-         * <br>
+         * Offer output from an arbitrary source. Only allowed in non-blocking mode.<br/>
+         * <br/>
          * After this method returns, the specified section of the buffer will have been
          * processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to
-         * transmit to the other peer.<br>
-         * <br>
+         * transmit to the other peer.<br/>
+         * <br/>
          * This method must not be called until after the handshake is complete! Attempting
          * to call it before the handshake is complete will result in an exception.
          * @param buffer The buffer containing application data to encrypt
@@ -710,8 +710,8 @@ namespace Org.BouncyCastle.Crypto.Tls
         /**
          * Gets the amount of encrypted data available to be sent. A call to
          * {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at
-         * least this much data.<br>
-         * <br>
+         * least this much data.<br/>
+         * <br/>
          * Only allowed in non-blocking mode.
          * @return The number of bytes of available encrypted data
          */
@@ -727,8 +727,8 @@ namespace Org.BouncyCastle.Crypto.Tls
          * Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check
          * how much encrypted data is currently available. This method functions similarly to
          * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data
-         * is available, nothing will be copied and zero will be returned.<br>
-         * <br>
+         * is available, nothing will be copied and zero will be returned.<br/>
+         * <br/>
          * Only allowed in non-blocking mode.
          * @param buffer The buffer to hold the encrypted data
          * @param offset The start offset in the buffer at which the data is written
diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs
index 27f7a1dfd..4ab628b13 100644
--- a/crypto/src/crypto/tls/TlsServerProtocol.cs
+++ b/crypto/src/crypto/tls/TlsServerProtocol.cs
@@ -45,12 +45,12 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Constructor for non-blocking mode.<br>
-         * <br>
+         * Constructor for non-blocking mode.<br/>
+         * <br/>
          * When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to
          * provide the received ciphertext, then use
-         * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.<br>
-         * <br>
+         * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.<br/>
+         * <br/>
          * Similarly, when data needs to be sent, use
          * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use
          * {@link #readOutput(byte[], int, int)} to get the corresponding
@@ -65,8 +65,8 @@ namespace Org.BouncyCastle.Crypto.Tls
         }
 
         /**
-         * Receives a TLS handshake in the role of server.<br>
-         * <br>
+         * Receives a TLS handshake in the role of server.<br/>
+         * <br/>
          * In blocking mode, this will not return until the handshake is complete.
          * In non-blocking mode, use {@link TlsPeer#notifyHandshakeComplete()} to
          * receive a callback when the handshake is complete.
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index 2a2e63961..06868eab1 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -271,26 +271,55 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <summary>
 		/// Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1).
 		/// </summary>
-		public void AddMethod(
-			char[] passPhrase) 
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        [Obsolete("Use version that takes an explicit s2kDigest parameter")]
+        public void AddMethod(char[] passPhrase)
 		{
 			AddMethod(passPhrase, HashAlgorithmTag.Sha1);
 		}
 
-		/// <summary>Add a PBE encryption method to the encrypted object.</summary>
-        public void AddMethod(
- 			char[]				passPhrase,
-			HashAlgorithmTag	s2kDigest)
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public void AddMethod(char[] passPhrase, HashAlgorithmTag s2kDigest)
+        {
+            DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, s2kDigest);
+        }
+
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public void AddMethodUtf8(char[] passPhrase, HashAlgorithmTag s2kDigest)
+        {
+            DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, s2kDigest);
+        }
+
+        /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public void AddMethodRaw(byte[] rawPassPhrase, HashAlgorithmTag s2kDigest)
+        {
+            DoAddMethod(rawPassPhrase, false, s2kDigest);
+        }
+
+        internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgorithmTag s2kDigest)
         {
             byte[] iv = new byte[8];
-			rand.NextBytes(iv);
+            rand.NextBytes(iv);
 
-			S2k s2k = new S2k(s2kDigest, iv, 0x60);
+            S2k s2k = new S2k(s2kDigest, iv, 0x60);
 
-			methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.MakeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase)));
+            methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)));
         }
 
-		/// <summary>Add a public key encrypted session key to the encrypted object.</summary>
+        /// <summary>Add a public key encrypted session key to the encrypted object.</summary>
         public void AddMethod(
             PgpPublicKey key)
         {
diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs
index 92ea394a2..4f6a4b12f 100644
--- a/crypto/src/openpgp/PgpKeyRingGenerator.cs
+++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -17,7 +17,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         private SymmetricKeyAlgorithmTag	encAlgorithm;
         private HashAlgorithmTag            hashAlgorithm;
         private int                         certificationLevel;
-        private char[]                      passPhrase;
+        private byte[]                      rawPassPhrase;
 		private bool						useSha1;
 		private PgpKeyPair                  masterKey;
         private PgpSignatureSubpacketVector hashedPacketVector;
@@ -28,7 +28,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// Create a new key ring generator using old style checksumming. It is recommended to use
 		/// SHA1 checksumming where possible.
 		/// </summary>
-		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        /// <param name="certificationLevel">The certification level for keys on this ring.</param>
 		/// <param name="masterKey">The master key pair.</param>
 		/// <param name="id">The id to be associated with the ring.</param>
 		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
@@ -36,6 +40,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="hashedPackets">Packets to be included in the certification hash.</param>
 		/// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
 		/// <param name="rand">input secured random.</param>
+        [Obsolete("Use version taking an explicit 'useSha1' parameter instead")]
 		public PgpKeyRingGenerator(
 			int							certificationLevel,
 			PgpKeyPair					masterKey,
@@ -52,7 +57,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <summary>
 		/// Create a new key ring generator.
 		/// </summary>
-		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        /// <param name="certificationLevel">The certification level for keys on this ring.</param>
 		/// <param name="masterKey">The master key pair.</param>
 		/// <param name="id">The id to be associated with the ring.</param>
 		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
@@ -71,23 +80,86 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			PgpSignatureSubpacketVector	hashedPackets,
             PgpSignatureSubpacketVector	unhashedPackets,
             SecureRandom				rand)
+            : this(certificationLevel, masterKey, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+		/// <summary>
+		/// Create a new key ring generator.
+		/// </summary>
+		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+		/// <param name="masterKey">The master key pair.</param>
+		/// <param name="id">The id to be associated with the ring.</param>
+		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+        /// <param name="utf8PassPhrase">
+        /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion
+        /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier).
+        /// </param>
+        /// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+		/// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+		/// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+		/// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+		/// <param name="rand">input secured random.</param>
+        public PgpKeyRingGenerator(
+            int							certificationLevel,
+            PgpKeyPair					masterKey,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            bool                        utf8PassPhrase,
+            char[]						passPhrase,
+			bool						useSha1,
+			PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+            : this(certificationLevel, masterKey, id, encAlgorithm,
+                PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase),
+                useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <summary>
+		/// Create a new key ring generator.
+		/// </summary>
+		/// <param name="certificationLevel">The certification level for keys on this ring.</param>
+		/// <param name="masterKey">The master key pair.</param>
+		/// <param name="id">The id to be associated with the ring.</param>
+		/// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+		/// <param name="rawPassPhrase">The passPhrase to be used to protect secret keys.</param>
+		/// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+		/// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+		/// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+		/// <param name="rand">input secured random.</param>
+        public PgpKeyRingGenerator(
+            int							certificationLevel,
+            PgpKeyPair					masterKey,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            byte[]						rawPassPhrase,
+			bool						useSha1,
+			PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
         {
             this.certificationLevel = certificationLevel;
             this.masterKey = masterKey;
             this.id = id;
             this.encAlgorithm = encAlgorithm;
-            this.passPhrase = passPhrase;
+            this.rawPassPhrase = rawPassPhrase;
 			this.useSha1 = useSha1;
 			this.hashedPacketVector = hashedPackets;
             this.unhashedPacketVector = unhashedPackets;
             this.rand = rand;
 
-			keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand));
+			keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand));
         }
 
         /// <summary>
         /// Create a new key ring generator.
         /// </summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         /// <param name="certificationLevel">The certification level for keys on this ring.</param>
         /// <param name="masterKey">The master key pair.</param>
         /// <param name="id">The id to be associated with the ring.</param>
@@ -109,19 +181,85 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PgpSignatureSubpacketVector hashedPackets,
             PgpSignatureSubpacketVector unhashedPackets,
             SecureRandom                rand)
+            : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <summary>
+        /// Create a new key ring generator.
+        /// </summary>
+        /// <param name="certificationLevel">The certification level for keys on this ring.</param>
+        /// <param name="masterKey">The master key pair.</param>
+        /// <param name="id">The id to be associated with the ring.</param>
+        /// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+        /// <param name="hashAlgorithm">The hash algorithm.</param>
+        /// <param name="utf8PassPhrase">
+        /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion
+        /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier).
+        /// </param>
+        /// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+        /// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+        /// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+        /// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+        /// <param name="rand">input secured random.</param>
+        public PgpKeyRingGenerator(
+            int                         certificationLevel,
+            PgpKeyPair                  masterKey,
+            string                      id,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            HashAlgorithmTag            hashAlgorithm,
+            bool                        utf8PassPhrase,
+            char[]                      passPhrase,
+            bool                        useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom                rand)
+            : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm,
+                PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase),
+                useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <summary>
+        /// Create a new key ring generator.
+        /// </summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        /// <param name="certificationLevel">The certification level for keys on this ring.</param>
+        /// <param name="masterKey">The master key pair.</param>
+        /// <param name="id">The id to be associated with the ring.</param>
+        /// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+        /// <param name="hashAlgorithm">The hash algorithm.</param>
+        /// <param name="rawPassPhrase">The passPhrase to be used to protect secret keys.</param>
+        /// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+        /// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+        /// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+        /// <param name="rand">input secured random.</param>
+        public PgpKeyRingGenerator(
+            int                         certificationLevel,
+            PgpKeyPair                  masterKey,
+            string                      id,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            HashAlgorithmTag            hashAlgorithm,
+            byte[]                      rawPassPhrase,
+            bool                        useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom                rand)
         {
             this.certificationLevel = certificationLevel;
             this.masterKey = masterKey;
             this.id = id;
             this.encAlgorithm = encAlgorithm;
-            this.passPhrase = passPhrase;
+            this.rawPassPhrase = rawPassPhrase;
             this.useSha1 = useSha1;
             this.hashedPacketVector = hashedPackets;
             this.unhashedPacketVector = unhashedPackets;
             this.rand = rand;
             this.hashAlgorithm = hashAlgorithm;
 
-            keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand));
+            keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand));
         }
 
 		/// <summary>Add a subkey to the key ring to be generated with default certification.</summary>
@@ -172,7 +310,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 				subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey));
 
-				keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand));
+				keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm,
+                    rawPassPhrase, false, useSha1, rand, false));
 			}
             catch (PgpException e)
             {
@@ -202,7 +341,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             try
             {
-                var sGen = new PgpSignatureGenerator(masterKey.PublicKey.Algorithm, hashAlgorithm);
+                PgpSignatureGenerator sGen = new PgpSignatureGenerator(masterKey.PublicKey.Algorithm, hashAlgorithm);
 
                 //
                 // Generate the certification
@@ -215,7 +354,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 IList subSigs = Platform.CreateArrayList();
                 subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey));
 
-                keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand));
+                keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm,
+                    rawPassPhrase, false, useSha1, rand, false));
             }
             catch (PgpException)
             {
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index c5fe89407..f43f2f512 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -30,18 +30,43 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		}
 
 		/// <summary>Return the decrypted input stream, using the passed in passphrase.</summary>
-        public Stream GetDataStream(
-            char[] passPhrase)
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public Stream GetDataStream(char[] passPhrase)
+        {
+            return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, false), true);
+        }
+
+		/// <summary>Return the decrypted input stream, using the passed in passphrase.</summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public Stream GetDataStreamUtf8(char[] passPhrase)
+        {
+            return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, true), true);
+        }
+
+		/// <summary>Return the decrypted input stream, using the passed in passphrase.</summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public Stream GetDataStreamRaw(byte[] rawPassPhrase)
+        {
+            return DoGetDataStream(rawPassPhrase, false);
+        }
+
+        internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
         {
 			try
 			{
 				SymmetricKeyAlgorithmTag keyAlgorithm = keyData.EncAlgorithm;
 
-				KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(
-					keyAlgorithm, keyData.S2k, passPhrase);
-
+				KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(
+					keyAlgorithm, keyData.S2k, rawPassPhrase, clearPassPhrase);
 
-				byte[] secKeyData = keyData.GetSecKeyData();
+                byte[] secKeyData = keyData.GetSecKeyData();
 				if (secKeyData != null && secKeyData.Length > 0)
 				{
 					IBufferedCipher keyCipher = CipherUtilities.GetCipher(
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 1027393ce..0f472c1a4 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -30,18 +30,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PgpPrivateKey				privKey,
             PgpPublicKey				pubKey,
             SymmetricKeyAlgorithmTag	encAlgorithm,
-            char[]						passPhrase,
-            bool						useSha1,
-            SecureRandom				rand)
-            : this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false)
-        {
-        }
-
-        internal PgpSecretKey(
-            PgpPrivateKey				privKey,
-            PgpPublicKey				pubKey,
-            SymmetricKeyAlgorithmTag	encAlgorithm,
-            char[]						passPhrase,
+            byte[]						rawPassPhrase,
+            bool                        clearPassPhrase,
             bool						useSha1,
             SecureRandom				rand,
             bool						isMasterKey)
@@ -107,7 +97,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     byte[] encData;
                     if (pub.Version >= 4)
                     {
-                        encData = EncryptKeyData(keyData, encAlgorithm, passPhrase, rand, out s2k, out iv);
+                        encData = EncryptKeyData(keyData, encAlgorithm, rawPassPhrase, clearPassPhrase, rand, out s2k, out iv);
                     }
                     else
                     {
@@ -139,6 +129,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        [Obsolete("Use the constructor taking an explicit 'useSha1' parameter instead")]
         public PgpSecretKey(
             int							certificationLevel,
             PgpKeyPair					keyPair,
@@ -152,32 +147,151 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
         }
 
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public PgpSecretKey(
+            int							certificationLevel,
+            PgpKeyPair					keyPair,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            char[]						passPhrase,
+            bool						useSha1,
+            PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+            : this(certificationLevel, keyPair, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <remarks>
+        /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion
+        /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         public PgpSecretKey(
             int							certificationLevel,
             PgpKeyPair					keyPair,
             string						id,
             SymmetricKeyAlgorithmTag	encAlgorithm,
+            bool                        utf8PassPhrase,
             char[]						passPhrase,
             bool						useSha1,
             PgpSignatureSubpacketVector	hashedPackets,
             PgpSignatureSubpacketVector	unhashedPackets,
             SecureRandom				rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
+            : this(certificationLevel, keyPair, id, encAlgorithm,
+                PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true,
+                useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public PgpSecretKey(
+            int							certificationLevel,
+            PgpKeyPair					keyPair,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            byte[]						rawPassPhrase,
+            bool						useSha1,
+            PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+            : this(certificationLevel, keyPair, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        internal PgpSecretKey(
+            int							certificationLevel,
+            PgpKeyPair					keyPair,
+            string						id,
+            SymmetricKeyAlgorithmTag	encAlgorithm,
+            byte[]						rawPassPhrase,
+            bool                        clearPassPhrase,
+            bool						useSha1,
+            PgpSignatureSubpacketVector	hashedPackets,
+            PgpSignatureSubpacketVector	unhashedPackets,
+            SecureRandom				rand)
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets),
+                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
+        {
+        }
+
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public PgpSecretKey(
+            int                         certificationLevel,
+            PgpKeyPair                  keyPair,
+            string                      id,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            HashAlgorithmTag            hashAlgorithm,
+            char[]                      passPhrase,
+            bool                        useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom                rand)
+            : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
         {
         }
 
+        /// <remarks>
+        /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion
+        /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         public PgpSecretKey(
             int                         certificationLevel,
             PgpKeyPair                  keyPair,
             string                      id,
             SymmetricKeyAlgorithmTag    encAlgorithm,
             HashAlgorithmTag            hashAlgorithm,
+            bool                        utf8PassPhrase,
             char[]                      passPhrase,
             bool                        useSha1,
             PgpSignatureSubpacketVector hashedPackets,
             PgpSignatureSubpacketVector unhashedPackets,
             SecureRandom                rand)
-            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm), encAlgorithm, passPhrase, useSha1, rand, true)
+            : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm,
+                PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true,
+                useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public PgpSecretKey(
+            int                         certificationLevel,
+            PgpKeyPair                  keyPair,
+            string                      id,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            HashAlgorithmTag            hashAlgorithm,
+            byte[]                      rawPassPhrase,
+            bool                        useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom                rand)
+            : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)
+        {
+        }
+
+        internal PgpSecretKey(
+            int                         certificationLevel,
+            PgpKeyPair                  keyPair,
+            string                      id,
+            SymmetricKeyAlgorithmTag    encAlgorithm,
+            HashAlgorithmTag            hashAlgorithm,
+            byte[]                      rawPassPhrase,
+            bool                        clearPassPhrase,
+            bool                        useSha1,
+            PgpSignatureSubpacketVector hashedPackets,
+            PgpSignatureSubpacketVector unhashedPackets,
+            SecureRandom                rand)
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm),
+                encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
         {
         }
 
@@ -269,7 +383,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             SecureRandom				rand)
             : this(certificationLevel,
                 new PgpKeyPair(algorithm, pubKey, privKey, time),
-                id, encAlgorithm, passPhrase, hashedPackets, unhashedPackets, rand)
+                id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
         {
         }
 
@@ -367,8 +481,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             get { return pub.GetUserAttributes(); }
         }
 
-        private byte[] ExtractKeyData(
-            char[] passPhrase)
+        private byte[] ExtractKeyData(byte[] rawPassPhrase, bool clearPassPhrase)
         {
             SymmetricKeyAlgorithmTag encAlgorithm = secret.EncAlgorithm;
             byte[] encData = secret.GetSecretKeyData();
@@ -380,7 +493,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             // TODO Factor this block out as 'decryptData'
             try
             {
-                KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase);
+                KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, rawPassPhrase, clearPassPhrase);
                 byte[] iv = secret.GetIV();
                 byte[] data;
 
@@ -483,8 +596,34 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         }
 
         /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
-        public PgpPrivateKey ExtractPrivateKey(
-            char[] passPhrase)
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public PgpPrivateKey ExtractPrivateKey(char[] passPhrase)
+        {
+            return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, false), true);
+        }
+
+        /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public PgpPrivateKey ExtractPrivateKeyUtf8(char[] passPhrase)
+        {
+            return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, true), true);
+        }
+
+        /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public PgpPrivateKey ExtractPrivateKeyRaw(byte[] rawPassPhrase)
+        {
+            return DoExtractPrivateKey(rawPassPhrase, false);
+        }
+
+        internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassPhrase)
         {
             if (IsPrivateKeyEmpty)
                 return null;
@@ -492,7 +631,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PublicKeyPacket pubPk = secret.PublicKeyPacket;
             try
             {
-                byte[] data = ExtractKeyData(passPhrase);
+                byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase);
                 BcpgInputStream bcpgIn = BcpgInputStream.Wrap(new MemoryStream(data, false));
                 AsymmetricKeyParameter privateKey;
                 switch (pubPk.Algorithm)
@@ -652,6 +791,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// Return a copy of the passed in secret key, encrypted using a new password
         /// and the passed in algorithm.
         /// </summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         /// <param name="key">The PgpSecretKey to be copied.</param>
         /// <param name="oldPassPhrase">The current password for the key.</param>
         /// <param name="newPassPhrase">The new password for the key.</param>
@@ -664,11 +807,67 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             SymmetricKeyAlgorithmTag	newEncAlgorithm,
             SecureRandom				rand)
         {
+            return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, false),
+                PgpUtilities.EncodePassPhrase(newPassPhrase, false), true, newEncAlgorithm, rand);
+        }
+
+        /// <summary>
+        /// Return a copy of the passed in secret key, encrypted using a new password
+        /// and the passed in algorithm.
+        /// </summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        /// <param name="key">The PgpSecretKey to be copied.</param>
+        /// <param name="oldPassPhrase">The current password for the key.</param>
+        /// <param name="newPassPhrase">The new password for the key.</param>
+        /// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+        /// <param name="rand">Source of randomness.</param>
+        public static PgpSecretKey CopyWithNewPasswordUtf8(
+            PgpSecretKey				key,
+            char[]						oldPassPhrase,
+            char[]						newPassPhrase,
+            SymmetricKeyAlgorithmTag	newEncAlgorithm,
+            SecureRandom				rand)
+        {
+            return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, true),
+                PgpUtilities.EncodePassPhrase(newPassPhrase, true), true, newEncAlgorithm, rand);
+        }
+
+        /// <summary>
+        /// Return a copy of the passed in secret key, encrypted using a new password
+        /// and the passed in algorithm.
+        /// </summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        /// <param name="key">The PgpSecretKey to be copied.</param>
+        /// <param name="rawOldPassPhrase">The current password for the key.</param>
+        /// <param name="rawNewPassPhrase">The new password for the key.</param>
+        /// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+        /// <param name="rand">Source of randomness.</param>
+        public static PgpSecretKey CopyWithNewPasswordRaw(
+            PgpSecretKey				key,
+            byte[]						rawOldPassPhrase,
+            byte[]						rawNewPassPhrase,
+            SymmetricKeyAlgorithmTag	newEncAlgorithm,
+            SecureRandom				rand)
+        {
+            return DoCopyWithNewPassword(key, rawOldPassPhrase, rawNewPassPhrase, false, newEncAlgorithm, rand);
+        }
 
+        internal static PgpSecretKey DoCopyWithNewPassword(
+            PgpSecretKey				key,
+            byte[]						rawOldPassPhrase,
+            byte[]						rawNewPassPhrase,
+            bool                        clearPassPhrase,
+            SymmetricKeyAlgorithmTag	newEncAlgorithm,
+            SecureRandom				rand)
+        {
             if (key.IsPrivateKeyEmpty)
                 throw new PgpException("no private key in this SecretKey - public key present only.");
 
-            byte[]	rawKeyData = key.ExtractKeyData(oldPassPhrase);
+            byte[]	rawKeyData = key.ExtractKeyData(rawOldPassPhrase, clearPassPhrase);
             int		s2kUsage = key.secret.S2kUsage;
             byte[]	iv = null;
             S2k		s2k = null;
@@ -696,11 +895,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
             else
             {
+                if (s2kUsage == SecretKeyPacket.UsageNone)
+                {
+                    s2kUsage = SecretKeyPacket.UsageChecksum;
+                }
+
                 try
                 {
                     if (pubKeyPacket.Version >= 4)
                     {
-                        keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv);
+                        keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv);
                     }
                     else
                     {
@@ -749,7 +953,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         private static byte[] EncryptKeyData(
             byte[]						rawKeyData,
             SymmetricKeyAlgorithmTag	encAlgorithm,
-            char[]						passPhrase,
+            byte[]						rawPassPhrase,
+            bool                        clearPassPhrase,
             SecureRandom				random,
             out S2k						s2k,
             out byte[]					iv)
@@ -769,7 +974,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             random.NextBytes(s2kIV);
             s2k = new S2k(HashAlgorithmTag.Sha1, s2kIV, 0x60);
 
-            KeyParameter kp = PgpUtilities.MakeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase);
+            KeyParameter kp = PgpUtilities.DoMakeKeyFromPassPhrase(encAlgorithm, s2k, rawPassPhrase, clearPassPhrase);
 
             iv = new byte[c.GetBlockSize()];
             random.NextBytes(iv);
@@ -779,13 +984,42 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return c.DoFinal(rawKeyData);
         }
 
-        /**
-         * Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
-         *
-         * @return a secret key object.
-         */
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+        /// </summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey)
         {
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, pubKey);
+        }
+
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+        /// </summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey)
+        {
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, pubKey);
+        }
+
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+        /// </summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase, PgpPublicKey pubKey)
+        {
+            return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey);
+        }
+
+        internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
+        {
             SXprUtilities.SkipOpenParenthesis(inputStream);
 
             string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
@@ -826,7 +1060,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 SXprUtilities.SkipCloseParenthesis(inputStream);
 
-                byte[] dValue = GetDValue(inputStream, passPhrase, curveName);
+                byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName);
                 // TODO: check SHA-1 hash.
 
                 return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
@@ -836,13 +1070,45 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             throw new PgpException("unknown key type found");
         }
 
-        /**
-        * Parse a secret key from one of the GPG S expression keys.
-        *
-        * @return a secret key object.
-        */
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys.
+        /// </summary>
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
         {
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true);
+        }
+
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys.
+        /// </summary>
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase)
+        {
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true);
+        }
+
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys.
+        /// </summary>
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase)
+        {
+            return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false);
+        }
+
+        /// <summary>
+        /// Parse a secret key from one of the GPG S expression keys.
+        /// </summary>
+        internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase)
+        {
             SXprUtilities.SkipOpenParenthesis(inputStream);
 
             string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
@@ -891,7 +1157,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 SXprUtilities.SkipCloseParenthesis(inputStream);
 
-                byte[] dValue = GetDValue(inputStream, passPhrase, curveName);
+                byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName);
                 // TODO: check SHA-1 hash.
 
                 return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
@@ -901,7 +1167,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             throw new PgpException("unknown key type found");
         }
 
-        private static byte[] GetDValue(Stream inputStream, char[] passPhrase, string curveName)
+        private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
         {
             string type;
             SXprUtilities.SkipOpenParenthesis(inputStream);
@@ -932,7 +1198,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
 
             // TODO: recognise other algorithms
-            KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, passPhrase);
+            KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
 
             byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
 
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index e4551db07..65c07b2e2 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -193,13 +193,44 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return MakeKey(algorithm, keyBytes);
         }
 
-		public static KeyParameter MakeKeyFromPassPhrase(
-            SymmetricKeyAlgorithmTag	algorithm,
-            S2k							s2k,
-            char[]						passPhrase)
+        internal static byte[] EncodePassPhrase(char[] passPhrase, bool utf8)
+        {
+            return passPhrase == null
+                ? null
+                : utf8
+                ? Encoding.UTF8.GetBytes(passPhrase)
+                : Strings.ToByteArray(passPhrase);
+        }
+
+        /// <remarks>
+        /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+        /// the historical behaviour of the library (1.7 and earlier).
+        /// </remarks>
+        public static KeyParameter MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase)
+        {
+            return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, false), true);
+        }
+
+        /// <remarks>
+        /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+        /// </remarks>
+        public static KeyParameter MakeKeyFromPassPhraseUtf8(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase)
+        {
+            return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, true), true);
+        }
+
+        /// <remarks>
+        /// Allows the caller to handle the encoding of the passphrase to bytes.
+        /// </remarks>
+        public static KeyParameter MakeKeyFromPassPhraseRaw(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase)
+        {
+            return DoMakeKeyFromPassPhrase(algorithm, s2k, rawPassPhrase, false);
+        }
+
+        internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase, bool clearPassPhrase)
         {
 			int keySize = GetKeySize(algorithm);
-			byte[] pBytes = Encoding.UTF8.GetBytes(passPhrase);
+            byte[] pBytes = rawPassPhrase;
 			byte[] keyBytes = new byte[(keySize + 7) / 8];
 
 			int generatedBytes = 0;
@@ -308,12 +339,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				loopCount++;
             }
 
-			Array.Clear(pBytes, 0, pBytes.Length);
+            if (clearPassPhrase && rawPassPhrase != null)
+            {
+                Array.Clear(rawPassPhrase, 0, rawPassPhrase.Length);
+            }
 
-			return MakeKey(algorithm, keyBytes);
+            return MakeKey(algorithm, keyBytes);
         }
 
-		/// <summary>Write out the passed in file as a literal data packet.</summary>
+        /// <summary>Write out the passed in file as a literal data packet.</summary>
         public static void WriteFileToLiteralData(
             Stream		output,
             char		fileType,
diff --git a/crypto/test/src/crypto/agreement/test/AllTests.cs b/crypto/test/src/crypto/agreement/test/AllTests.cs
new file mode 100644
index 000000000..ea8f438e5
--- /dev/null
+++ b/crypto/test/src/crypto/agreement/test/AllTests.cs
@@ -0,0 +1,31 @@
+using System;
+
+using NUnit.Core;
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Agreement.Tests
+{
+    [TestFixture]
+    public class AllTests
+    {
+        public static void Main(string[] args)
+        {
+            Suite.Run(new NullListener(), NUnit.Core.TestFilter.Empty);
+        }
+
+        [Suite]
+        public static TestSuite Suite
+        {
+            get
+            {
+                TestSuite suite = new TestSuite("JPAKE Engine Tests");
+                suite.Add(new JPakeParticipantTest());
+                suite.Add(new JPakePrimeOrderGroupTest());
+                suite.Add(new JPakeUtilitiesTest());
+                return suite;
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs b/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs
new file mode 100644
index 000000000..c84264aa5
--- /dev/null
+++ b/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs
@@ -0,0 +1,566 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Agreement.JPake;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Agreement.Tests
+{
+    [TestFixture]
+    public class JPakeParticipantTest
+        : SimpleTest
+    {
+        public override void PerformTest()
+        {
+            TestConstruction();
+            TestSuccessfulExchange();
+            TestIncorrectPassword();
+            TestStateValidation();
+            TestValidateRound1PayloadReceived();
+            TestValidateRound2PayloadReceived();
+        }
+
+        public override string Name
+        {
+            get { return "JPakeParticipant"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new JPakeParticipantTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+
+        public void TestConstruction()
+        {
+            JPakePrimeOrderGroup group = JPakePrimeOrderGroups.SUN_JCE_1024;
+            SecureRandom random = new SecureRandom();
+            IDigest digest = new Sha256Digest();
+            string participantId = "participantId";
+            char[] password = "password".ToCharArray();
+
+            // should succeed
+            new JPakeParticipant(participantId, password, group, digest, random);
+
+            // null participantId
+            try
+            {
+                new JPakeParticipant(null, password, group, digest, random);
+
+                Fail("failed to throw exception on null participantId");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+
+            // null password
+            try
+            {
+                new JPakeParticipant(participantId, null, group, digest, random);
+
+                Fail("failed to throw exception on null password");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+
+            // empty password
+            try
+            {
+                new JPakeParticipant(participantId, "".ToCharArray(), group, digest, random);
+
+                Fail("failed to throw exception on empty password");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // null group
+            try
+            {
+                new JPakeParticipant(participantId, password, null, digest, random);
+
+                Fail("failed to throw exception on null group");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+
+            // null digest
+            try
+            {
+                new JPakeParticipant(participantId, password, group, null, random);
+
+                Fail("failed to throw exception on null digest");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+
+            // null random
+            try
+            {
+                new JPakeParticipant(participantId, password, group, digest, null);
+
+                Fail("failed to throw exception on null random");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+        }
+
+        public void TestSuccessfulExchange()
+        {
+            JPakeParticipant alice = CreateAlice();
+            JPakeParticipant bob = CreateBob();
+
+            ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob);
+
+            alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload);
+            bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload);
+
+            BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial();
+            BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial();
+
+            JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial);
+            JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial);
+
+            alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+            bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+
+            Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial);
+        }
+
+        public void TestIncorrectPassword()
+        {
+            JPakeParticipant alice = CreateAlice();
+            JPakeParticipant bob = CreateBobWithWrongPassword();
+
+            ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob);
+
+            alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload);
+            bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload);
+
+            BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial();
+            BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial();
+
+            JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial);
+            JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial);
+
+            try
+            {
+                alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+
+                Fail("failed to throw exception on incorrect password");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            try
+            {
+                bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+
+                Fail("failed to throw exception on incorrect password");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestStateValidation()
+        {
+            JPakeParticipant alice = CreateAlice();
+            JPakeParticipant bob = CreateBob();
+
+            // We're testing alice here. Bob is just used for help.
+
+            // START ROUND 1 CHECKS
+
+            Assert.AreEqual(JPakeParticipant.STATE_INITIALIZED, alice.State);
+
+            // create round 2 before round 1
+            try
+            {
+                alice.CreateRound2PayloadToSend();
+
+                Fail("failed to throw on round 2 creation before 1");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend();
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_CREATED, alice.State);
+
+            // create round 1 twice
+            try
+            {
+                alice.CreateRound1PayloadToSend();
+
+                Fail("failed to throw on round 1 creation twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            // create round 2 before validation round 1
+            try
+            {
+                alice.CreateRound2PayloadToSend();
+
+                Fail("failed to throw on round 2 creation before round 1 validation");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            // validate round 2 before validation round 1
+            try
+            {
+                alice.ValidateRound2PayloadReceived(null);
+
+                Fail("failed to throw on round 2 validation before round 1 validation");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend();
+            alice.ValidateRound1PayloadReceived(bobRound1Payload);
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_VALIDATED, alice.State);
+
+            // validate round 1 payload twice
+            try
+            {
+                alice.ValidateRound1PayloadReceived(bobRound1Payload);
+
+                Fail("failed to throw on round 1 validation twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            bob.ValidateRound1PayloadReceived(aliceRound1Payload);
+
+            // START ROUND 2 CHECKS
+
+            JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend();
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_CREATED, alice.State);
+
+            // create round 2 payload twice
+            try
+            {
+                alice.CreateRound2PayloadToSend();
+
+                Fail("failed to throw on round 2 creation twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            // create key before validation round 2
+            try
+            {
+                alice.CalculateKeyingMaterial();
+
+                Fail("failed to throw on calculating keying material before round 2 validation");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            // validate round 3 before validating round 2
+            try
+            {
+                alice.ValidateRound3PayloadReceived(null, null);
+
+                Fail("failed to throw on validating round 3 before 2");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend();
+            alice.ValidateRound2PayloadReceived(bobRound2Payload);
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_VALIDATED, alice.State);
+
+            // validate round 2 payload twice
+            try
+            {
+                alice.ValidateRound2PayloadReceived(bobRound2Payload);
+
+                Fail("failed to throw on validating round 2 twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            bob.ValidateRound2PayloadReceived(aliceRound2Payload);
+
+            // create round 3 before calculating key
+            try
+            {
+                alice.CreateRound3PayloadToSend(BigInteger.One);
+
+                Fail("failed to throw on creating round 3 before calculating key aterial");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            // START KEY CALCULATION CHECKS
+
+            BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial();
+            Assert.AreEqual(JPakeParticipant.STATE_KEY_CALCULATED, alice.State);
+
+            // calculate key twice
+            try
+            {
+                alice.CalculateKeyingMaterial();
+
+                Fail("failed to throw on calculating key twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial();
+
+            // START ROUND 3 CHECKS
+
+            JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial);
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_CREATED, alice.State);
+
+            // create round 3 payload twice
+            try
+            {
+                alice.CreateRound3PayloadToSend(aliceKeyingMaterial);
+
+                Fail("failed to throw on creation round 3 twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial);
+            alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+            Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_VALIDATED, alice.State);
+
+            // validate round 3 payload twice
+            try
+            {
+                alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial);
+
+                Fail("failed to throw on validation round 3 twice");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+
+            bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial);
+        }
+
+        public void TestValidateRound1PayloadReceived()
+        {
+            // We're testing alice here. Bob is just used for help.
+
+            JPakeRound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend();
+
+            // should succeed
+            CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload);
+
+            // alice verifies alice's payload
+            try
+            {
+                JPakeParticipant alice = CreateAlice();
+                alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend());
+
+                Fail("failed to throw on participant validating own payload");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // g^x4 == 1
+            try
+            {
+                CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload(
+                    bobRound1Payload.ParticipantId,
+                    bobRound1Payload.Gx1,
+                    BigInteger.One,
+                    bobRound1Payload.KnowledgeProofForX1,
+                    bobRound1Payload.KnowledgeProofForX2));
+
+                Fail("failed to throw on g^x4 == 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // zero knowledge proof for x3 fails
+            try
+            {
+                JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend();
+                CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload(
+                    bobRound1Payload.ParticipantId,
+                    bobRound1Payload.Gx1,
+                    bobRound1Payload.Gx2,
+                    bobRound1Payload2.KnowledgeProofForX1,
+                    bobRound1Payload.KnowledgeProofForX2));
+
+                Fail("failed to throw on incorrect zero knowledge proof for x3");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // zero knowledge proof for x4 fails
+            try
+            {
+                JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend();
+                CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload(
+                    bobRound1Payload.ParticipantId,
+                    bobRound1Payload.Gx1,
+                    bobRound1Payload.Gx2,
+                    bobRound1Payload.KnowledgeProofForX1,
+                    bobRound1Payload2.KnowledgeProofForX2));
+
+                Fail("failed to throw on incorrect zero knowledge proof for x4");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateRound2PayloadReceived()
+        {
+            // We're testing alice here. Bob is just used for help.
+
+            // should succeed
+            ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob());
+            exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload);
+
+            // alice verified alice's payload
+            ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob());
+            try
+            {
+                exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload);
+
+                Fail("failed to throw on participant verifying own payload 2");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // wrong z
+            ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob());
+            ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob());
+            try
+            {
+                exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload);
+
+                Fail("failed to throw on wrong z");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        private class ExchangeAfterRound2Creation
+        {
+            public JPakeParticipant alice;
+            public JPakeRound2Payload aliceRound2Payload;
+            public JPakeRound2Payload bobRound2Payload;
+
+            public ExchangeAfterRound2Creation(
+                JPakeParticipant alice,
+                JPakeRound2Payload aliceRound2Payload,
+                JPakeRound2Payload bobRound2Payload)
+            {
+                this.alice = alice;
+                this.aliceRound2Payload = aliceRound2Payload;
+                this.bobRound2Payload = bobRound2Payload;
+            }
+        }
+
+        private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPakeParticipant alice, JPakeParticipant bob)
+        {
+            JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend();
+            JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend();
+
+            alice.ValidateRound1PayloadReceived(bobRound1Payload);
+            bob.ValidateRound1PayloadReceived(aliceRound1Payload);
+
+            JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend();
+            JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend();
+
+            return new ExchangeAfterRound2Creation(
+                alice,
+                aliceRound2Payload,
+                bobRound2Payload);
+        }
+
+        private JPakeParticipant CreateAlice()
+        {
+            return CreateParticipant("alice", "password");
+        }
+
+        private JPakeParticipant CreateBob()
+        {
+            return CreateParticipant("bob", "password");
+        }
+
+        private JPakeParticipant CreateBobWithWrongPassword()
+        {
+            return CreateParticipant("bob", "wrong");
+        }
+
+        private JPakeParticipant CreateParticipant(string participantId, string password)
+        {
+            return new JPakeParticipant(
+                participantId,
+                password.ToCharArray(),
+                JPakePrimeOrderGroups.SUN_JCE_1024);
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs b/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs
new file mode 100644
index 000000000..0f089f93c
--- /dev/null
+++ b/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs
@@ -0,0 +1,117 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Agreement.JPake;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Agreement.Tests
+{
+    [TestFixture]
+    public class JPakePrimeOrderGroupTest
+        : SimpleTest
+    {
+        public override void PerformTest()
+        {
+            TestConstruction();
+        }
+
+        public override string Name
+        {
+            get { return "JPakePrimeOrderGroup"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new JPakePrimeOrderGroupTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+
+        public void TestConstruction()
+        {
+            // p-1 not evenly divisible by q
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(5), BigInteger.ValueOf(6));
+
+                Fail("failed to throw exception on p-1 not evenly divisible by q");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // g < 2
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(1));
+
+                Fail("failed to throw exception on g < 2");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // g > p - 1
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(11));
+
+                Fail("failed to throw exception on g > p - 1");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            //g^q mod p not equal 1
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(6));
+
+                Fail("failed to throw exception on g^q mod p not equal 1");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // p not prime
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(15), BigInteger.ValueOf(2), BigInteger.ValueOf(4));
+
+                Fail("failed to throw exception on p not prime");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // q not prime
+            try
+            {
+                new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(6), BigInteger.ValueOf(3));
+
+                Fail("failed to throw exception on q not prime");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+
+            // should succeed
+            new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(3), BigInteger.ValueOf(4));
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs b/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs
new file mode 100644
index 000000000..04a52cc06
--- /dev/null
+++ b/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs
@@ -0,0 +1,306 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Agreement.JPake;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Agreement.Tests
+{
+    [TestFixture]
+    public class JPakeUtilitiesTest
+        : SimpleTest
+    {
+        private static readonly BigInteger Ten = BigInteger.ValueOf(10);
+
+        public override void PerformTest()
+        {
+            TestValidateGx4();
+            TestValidateGa();
+            TestValidateParticipantIdsDiffer();
+            TestValidateParticipantsIdsEqual();
+            TestValidateMacTag();
+            TestValidateNotNull();
+            TestValidateZeroKnowledgeProof();
+        }
+
+        public override string Name
+        {
+            get { return "JPakeUtilities"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new JPakeUtilitiesTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+
+        public void TestValidateGx4()
+        {
+            JPakeUtilities.ValidateGx4(Ten);
+
+            try
+            {
+                JPakeUtilities.ValidateGx4(BigInteger.One);
+
+                Fail("exception not thrown for g^x4 equal to 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateGa()
+        {
+            JPakeUtilities.ValidateGa(Ten);
+
+            try
+            {
+                JPakeUtilities.ValidateGa(BigInteger.One);
+
+                Fail("exception not thrown for g^a equal to 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateParticipantIdsDiffer()
+        {
+            JPakeUtilities.ValidateParticipantIdsDiffer("a", "b");
+            JPakeUtilities.ValidateParticipantIdsDiffer("a", "A");
+
+            try
+            {
+                JPakeUtilities.ValidateParticipantIdsDiffer("a", "a");
+
+                Fail("validate participant ids differ not throwing exception for equal participant ids");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateParticipantsIdsEqual()
+        {
+            JPakeUtilities.ValidateParticipantIdsEqual("a", "a");
+
+            try
+            {
+                JPakeUtilities.ValidateParticipantIdsEqual("a", "b");
+
+                Fail("validate participant ids equal not throwing exception for different participant ids");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateMacTag()
+        {
+            JPakePrimeOrderGroup pg1 = JPakePrimeOrderGroups.SUN_JCE_1024;
+
+            SecureRandom random = new SecureRandom();
+            IDigest digest = new Sha256Digest();
+
+            BigInteger x1 = JPakeUtilities.GenerateX1(pg1.Q, random);
+            BigInteger x2 = JPakeUtilities.GenerateX2(pg1.Q, random);
+            BigInteger x3 = JPakeUtilities.GenerateX1(pg1.Q, random);
+            BigInteger x4 = JPakeUtilities.GenerateX2(pg1.Q, random);
+
+            BigInteger gx1 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x1);
+            BigInteger gx2 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x2);
+            BigInteger gx3 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x3);
+            BigInteger gx4 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x4);
+
+            BigInteger gB = JPakeUtilities.CalculateGA(pg1.P, gx3, gx1, gx2);
+
+            BigInteger s = JPakeUtilities.CalculateS("password".ToCharArray());
+
+            BigInteger xs = JPakeUtilities.CalculateX2s(pg1.Q, x4, s);
+
+            BigInteger B = JPakeUtilities.CalculateA(pg1.P, pg1.Q, gB, xs);
+
+            BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(pg1.P, pg1.Q, gx4, x2, s, B);
+
+            BigInteger macTag = JPakeUtilities.CalculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest);
+
+            // should succeed
+            JPakeUtilities.ValidateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag);
+
+            // validating own macTag (as opposed to the other party's mactag)
+            try
+            {
+                JPakeUtilities.ValidateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest, macTag);
+
+                Fail("failed to throw exception on validating own macTag (calculated partner macTag)");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // participant ids switched
+            try
+            {
+                JPakeUtilities.ValidateMacTag("participantId", "partnerParticipantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag);
+
+                Fail("failed to throw exception on validating own macTag (calculated partner macTag");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateNotNull()
+        {
+            JPakeUtilities.ValidateNotNull("a", "description");
+
+            try
+            {
+                JPakeUtilities.ValidateNotNull(null, "description");
+
+                Fail("failed to throw exception on null");
+            }
+            catch (ArgumentNullException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateZeroKnowledgeProof()
+        {
+            JPakePrimeOrderGroup pg1 = JPakePrimeOrderGroups.SUN_JCE_1024;
+
+            SecureRandom random = new SecureRandom();
+            IDigest digest1 = new Sha256Digest();
+
+            BigInteger x1 = JPakeUtilities.GenerateX1(pg1.Q, random);
+            BigInteger gx1 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x1);
+            string participantId1 = "participant1";
+
+            BigInteger[] zkp1 = JPakeUtilities.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, x1, participantId1, digest1, random);
+
+            // should succeed
+            JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest1);
+
+            // wrong group
+            JPakePrimeOrderGroup pg2 = JPakePrimeOrderGroups.NIST_3072;
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg2.P, pg2.Q, pg2.G, gx1, zkp1, participantId1, digest1);
+
+                Fail("failed to throw exception on wrong prime order group");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // wrong digest
+            IDigest digest2 = new Sha1Digest();
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest2);
+
+                Fail("failed to throw exception on wrong digest");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // wrong participant
+            string participantId2 = "participant2";
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId2, digest1);
+
+                Fail("failed to throw exception on wrong participant");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // wrong gx
+            BigInteger x2 = JPakeUtilities.GenerateX2(pg1.Q, random);
+            BigInteger gx2 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x2);
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, zkp1, participantId1, digest1);
+
+                Fail("failed to throw exception on wrong gx");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // wrong zkp
+            BigInteger[] zkp2 = JPakeUtilities.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, x2, participantId1, digest1, random);
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp2, participantId1, digest1);
+
+                Fail("failed to throw exception on wrong zero knowledge proof");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // gx <= 0
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, BigInteger.Zero, zkp1, participantId1, digest1);
+
+                Fail("failed to throw exception on g^x <= 0");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // gx >= p
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.P, zkp1, participantId1, digest1);
+
+                Fail("failed to throw exception on g^x >= p");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+
+            // gx mod q == 1
+            try
+            {
+                JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.Q.Add(BigInteger.One), zkp1, participantId1, digest1);
+
+                Fail("failed to throw exception on g^x mod q == 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs b/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs
index 5fe0f32ad..477e287f1 100644
--- a/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs
+++ b/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs
@@ -104,7 +104,7 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
                 protocol.OfferInput(new byte[10]);
                 Assert.Fail("Input was accepted after close");
             }
-            catch (IOException e)
+            catch (IOException)
             {
             }
 
@@ -113,7 +113,7 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests
                 protocol.OfferOutput(new byte[10], 0, 10);
                 Assert.Fail("Output was accepted after close");
             }
-            catch (IOException e)
+            catch (IOException)
             {
             }
         }
diff --git a/crypto/test/src/openpgp/examples/ByteArrayHandler.cs b/crypto/test/src/openpgp/examples/ByteArrayHandler.cs
index 676db8766..b5098ff66 100644
--- a/crypto/test/src/openpgp/examples/ByteArrayHandler.cs
+++ b/crypto/test/src/openpgp/examples/ByteArrayHandler.cs
@@ -123,7 +123,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Examples
 			}
 
 			PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator(algorithm, new SecureRandom());
-            encGen.AddMethod(passPhrase);
+            encGen.AddMethod(passPhrase, HashAlgorithmTag.Sha1);
 
 			Stream encOut = encGen.Open(output, compressedData.Length);
 
diff --git a/crypto/test/src/openpgp/examples/PbeFileProcessor.cs b/crypto/test/src/openpgp/examples/PbeFileProcessor.cs
index 66b1cc4ed..961704407 100644
--- a/crypto/test/src/openpgp/examples/PbeFileProcessor.cs
+++ b/crypto/test/src/openpgp/examples/PbeFileProcessor.cs
@@ -127,7 +127,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Examples
 
 				PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator(
 					SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
-				encGen.AddMethod(passPhrase);
+                encGen.AddMethod(passPhrase, HashAlgorithmTag.Sha1);
 
 				Stream encOut = encGen.Open(outputStream, compressedData.Length);
 
diff --git a/crypto/test/src/openpgp/test/PGPPBETest.cs b/crypto/test/src/openpgp/test/PGPPBETest.cs
index 621cef684..29b786a83 100644
--- a/crypto/test/src/openpgp/test/PGPPBETest.cs
+++ b/crypto/test/src/openpgp/test/PGPPBETest.cs
@@ -168,7 +168,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
             PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(
 				SymmetricKeyAlgorithmTag.Cast5, new SecureRandom());
 
-            cPk.AddMethod(pass);
+            cPk.AddMethod(pass, HashAlgorithmTag.Sha1);
 
 			byte[] bOutData = bOut.ToArray();
 			Stream cOut = cPk.Open(new UncloseableStream(cbOut), bOutData.Length);
@@ -188,7 +188,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
 			cPk = new PgpEncryptedDataGenerator(
 				SymmetricKeyAlgorithmTag.Cast5, new SecureRandom());
 
-			cPk.AddMethod(pass);
+            cPk.AddMethod(pass, HashAlgorithmTag.Sha1);
 
 			bOutData = bOut.ToArray();
 			cOut = cPk.Open(new UncloseableStream(cbOut), bOutData.Length);
@@ -233,7 +233,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
             cPk = new PgpEncryptedDataGenerator(
 				SymmetricKeyAlgorithmTag.Cast5, rand);
 
-            cPk.AddMethod(pass);
+            cPk.AddMethod(pass, HashAlgorithmTag.Sha1);
 
 			cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]);
             {
@@ -256,7 +256,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
             cPk = new PgpEncryptedDataGenerator(
 				SymmetricKeyAlgorithmTag.Cast5, true, rand);
 
-            cPk.AddMethod(pass);
+            cPk.AddMethod(pass, HashAlgorithmTag.Sha1);
 
             cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]);
             bOutData = bOut.ToArray();
@@ -328,7 +328,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
 			cbOut = new MemoryStream();
 			cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, true, rand);
 
-			cPk.AddMethod(pass);
+            cPk.AddMethod(pass, HashAlgorithmTag.Sha1);
 
 			cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]);
 
diff --git a/crypto/test/src/openpgp/test/PGPRSATest.cs b/crypto/test/src/openpgp/test/PGPRSATest.cs
index 35f844483..82b569bbb 100644
--- a/crypto/test/src/openpgp/test/PGPRSATest.cs
+++ b/crypto/test/src/openpgp/test/PGPRSATest.cs
@@ -381,7 +381,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
 
             encGen.AddMethod(pgpPubKey);
 
-            encGen.AddMethod("password".ToCharArray());
+            encGen.AddMethod("password".ToCharArray(), HashAlgorithmTag.Sha1);
 
             Stream cOut = encGen.Open(bcOut, bytes.Length);
 
diff --git a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
index 9896c1ef6..43aef5afa 100644
--- a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
+++ b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
@@ -1844,7 +1844,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
             PgpKeyPair elgKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ElGamalEncrypt, elgKp, DateTime.UtcNow);
 
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, dsaKeyPair,
-                "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, new SecureRandom());
+                "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, new SecureRandom());
 
             keyRingGen.AddSubKey(elgKeyPair);
 
@@ -1904,12 +1904,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
             PgpKeyPair rsaKeyPair2 = new PgpKeyPair(PublicKeyAlgorithmTag.RsaGeneral, rsaKp, DateTime.UtcNow);
 
             PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification,
-                rsaKeyPair1, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, random);
+                rsaKeyPair1, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, random);
             PgpSecretKeyRing secRing1 = keyRingGen.GenerateSecretKeyRing();
             PgpPublicKeyRing pubRing1 = keyRingGen.GeneratePublicKeyRing();
 
             keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification,
-                rsaKeyPair2, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, random);
+                rsaKeyPair2, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, random);
             PgpSecretKeyRing secRing2 = keyRingGen.GenerateSecretKeyRing();
             PgpPublicKeyRing pubRing2 = keyRingGen.GeneratePublicKeyRing();
 
diff --git a/crypto/test/src/openpgp/test/PgpUnicodeTest.cs b/crypto/test/src/openpgp/test/PgpUnicodeTest.cs
index ce1df8980..534e8a471 100644
--- a/crypto/test/src/openpgp/test/PgpUnicodeTest.cs
+++ b/crypto/test/src/openpgp/test/PgpUnicodeTest.cs
@@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
     [TestFixture]
     public class PgpUnicodeTest
     {
-        private void DoTestKey(BigInteger keyId, string passphrase)
+        private void DoTestKey(BigInteger keyId, string passphrase, bool utf8)
         {
             PgpSecretKeyRingBundle secretKeyRing = LoadSecretKeyCollection("secring.gpg");
 
@@ -25,7 +25,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
 
             try
             {
-                PgpPrivateKey privateKey = key.ExtractPrivateKey(passphrase.ToCharArray());
+                char[] pass = passphrase.ToCharArray();
+
+                PgpPrivateKey privateKey = utf8
+                    ?   key.ExtractPrivateKeyUtf8(pass)
+                    :   key.ExtractPrivateKey(pass);
 
                 Assert.IsTrue(privateKey.KeyId == keyId.LongValue);
             }
@@ -53,7 +57,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
  //             passwordFile.close();
  //             String passphrase = new String(password);            
 
-                DoTestKey(keyId, passphrase);
+                DoTestKey(keyId, passphrase, true);
 
                 // all fine!
 
@@ -75,7 +79,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
 
                 string passphrase = "Admin123";
 
-                DoTestKey(keyId, passphrase);
+                DoTestKey(keyId, passphrase, false);
+                DoTestKey(keyId, passphrase, true);
 
                 // all fine!
             }
@@ -102,7 +107,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
                 string passphrase = reader.ReadLine();
                 passwordFile.Close();
 
-                DoTestKey(keyId, passphrase);
+                DoTestKey(keyId, passphrase, true);
 
                 // all fine!
             }