summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex van Poppelen <avanpoppelen@gmail.com>2015-10-17 02:25:54 +0200
committerAlex van Poppelen <avanpoppelen@gmail.com>2015-10-17 02:25:54 +0200
commit9b1a5b045607b31d724e72b88112b192138f8a68 (patch)
tree2c80b7a1aed25eb9be6853a501eef9894600a6c5
parentPort of non-blocking TLS API from Java (diff)
downloadBouncyCastle.NET-ed25519-9b1a5b045607b31d724e72b88112b192138f8a68.tar.xz
ported jpake library and tests from java
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPAKEParticipant.cs525
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs117
-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
-rwxr-xr-xcrypto/src/crypto/agreement/jpake/JPAKEUtil.cs404
-rw-r--r--crypto/test/src/crypto/test/JPAKEParticipantTest.cs566
-rw-r--r--crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs117
-rw-r--r--crypto/test/src/crypto/test/JPAKEUtilTest.cs306
10 files changed, 2367 insertions, 0 deletions
diff --git a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs
new file mode 100755
index 000000000..f36069262
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs
@@ -0,0 +1,525 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+
+/**
+ * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
+ * <p>
+ * 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>
+ * <p>
+ * 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 {@link JPAKEParticipant} represents one participant, and
+ * is the primary interface for executing the exchange.
+ * <p>
+ * To execute an exchange, construct a {@link 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):
+ * <ol>
+ * <li>{@link #createRound1PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #createRound2PayloadToSend()} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant</li>
+ * <li>{@link #calculateKeyingMaterial()}</li>
+ * <li>{@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant</li>
+ * <li>{@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant</li>
+ * </ol>
+ * <p>
+ * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}.
+ * The caller is responsible for deriving the session key using a secure key derivation function (KDF).
+ * <p>
+ * 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.)
+ * <p>
+ * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * This class is stateful and NOT threadsafe.
+ * Each instance should only be used for ONE complete J-PAKE exchange
+ * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange).
+ * <p>
+ */
+namespace Org.BouncyCastle.Crypto.Agreement.Jpake
+{
+    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 {@link #calculateKeyingMaterial()}.
+         * <p>
+         * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's,
+         * and the field is set to null.
+         * </p>
+         */
+        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;
+
+        /**
+         * Convenience constructor for a new {@link JPAKEParticipant} that uses
+         * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group,
+         * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+         * <p>
+         * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+         *
+         * @param participantId unique identifier of this participant.
+         *                      The two participants in the exchange must NOT share the same id.
+         * @param password      shared secret.
+         *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+         *                      Caller should clear the input password as soon as possible.
+         * @throws NullReferenceException if any argument is null
+         * @throws ArgumentException if password is empty
+         */
+        public JPAKEParticipant(string participantId, char[] password)
+            : this(participantId, password, JPAKEPrimeOrderGroups.NIST_3072) { }
+
+        /**
+         * Convenience constructor for a new {@link JPAKEParticipant} that uses
+         * a SHA-256 digest and a default {@link SecureRandom} implementation.
+         * <p>
+         * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+         *
+         * @param participantId unique identifier of this participant.
+         *                      The two participants in the exchange must NOT share the same id.
+         * @param password      shared secret.
+         *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+         *                      Caller should clear the input password as soon as possible.
+         * @param group         prime order group.
+         *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+         * @throws NullReferenceException if any argument is null
+         * @throws ArgumentException if password is empty
+         */
+        public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group)
+            : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { }
+
+
+        /**
+         * Construct a new {@link JPAKEParticipant}.
+         * <p>
+         * After construction, the {@link #getState() state} will be  {@link #STATE_INITIALIZED}.
+         *
+         * @param participantId unique identifier of this participant.
+         *                      The two participants in the exchange must NOT share the same id.
+         * @param password      shared secret.
+         *                      A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called).
+         *                      Caller should clear the input password as soon as possible.
+         * @param group         prime order group.
+         *                      See {@link JPAKEPrimeOrderGroups} for standard groups
+         * @param digest        digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred)
+         * @param random        source of secure random data for x1 and x2, and for the zero knowledge proofs
+         * @throws NullReferenceException if any argument is null
+         * @throws IllegalArgumentException if password is empty
+         */
+        public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group, IDigest digest, SecureRandom random)
+        {
+            JPAKEUtil.ValidateNotNull(participantId, "participantId");
+            JPAKEUtil.ValidateNotNull(password, "password");
+            JPAKEUtil.ValidateNotNull(group, "p");
+            JPAKEUtil.ValidateNotNull(digest, "digest");
+            JPAKEUtil.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 {@link #calculateKeyingMaterial()} is called.
+             * 
+             * i.e. When {@link #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;
+        }
+
+        /**
+         * Gets the current state of this participant.
+         * See the <tt>STATE_*</tt> constants for possible values.
+         */
+        public int State
+        {
+            get { return state; }
+        }
+
+
+        /**
+         * Creates and returns the payload to send to the other participant during round 1.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_CREATED}.
+         */
+        public JPAKERound1Payload CreateRound1PayloadToSend()
+        {
+            if (this.state >= STATE_ROUND_1_CREATED)
+            {
+                throw new InvalidOperationException("Round 1 payload already created for " + this.participantId);
+            }
+
+            this.x1 = JPAKEUtil.GenerateX1(q, random);
+            this.x2 = JPAKEUtil.GenerateX2(q, random);
+
+            this.gx1 = JPAKEUtil.CalculateGx(p, g, x1);
+            this.gx2 = JPAKEUtil.CalculateGx(p, g, x2);
+            BigInteger[] knowledgeProofForX1 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
+            BigInteger[] knowledgeProofForX2 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
+
+            this.state = STATE_ROUND_1_CREATED;
+
+            return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
+        }
+
+        /**
+         * Validates the payload received from the other participant during round 1.
+         * <p>
+         * Must be called prior to {@link #createRound2PayloadToSend()}.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_1_VALIDATED}.
+         *
+         * @throws CryptoException if validation fails.
+         * @throws InvalidOperationException if called multiple times.
+         */
+        public 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;
+
+            JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId);
+            JPAKEUtil.ValidateGx4(gx4);
+            JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest);
+            JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest);
+
+            this.state = STATE_ROUND_1_VALIDATED;
+        }
+
+        /**
+         * Creates and returns the payload to send to the other participant during round 2.
+         * <p>
+         * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_CREATED}.
+         *
+         * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+         */
+        public 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 = JPAKEUtil.CalculateGA(p, gx1, gx3, gx4);
+            BigInteger s = JPAKEUtil.CalculateS(password);
+            BigInteger x2s = JPAKEUtil.CalculateX2s(q, x2, s);
+            BigInteger A = JPAKEUtil.CalculateA(p, q, gA, x2s);
+            BigInteger[] knowledgeProofForX2s = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
+
+            this.state = STATE_ROUND_2_CREATED;
+
+            return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s);
+        }
+
+        /**
+         * Validates the payload received from the other participant during round 2.
+         * <p>
+         * 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)
+         * <p>
+         * Must be called prior to {@link #calculateKeyingMaterial()}.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_2_VALIDATED}.
+         *
+         * @throws CryptoException if validation fails.
+         * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times
+         */
+        public 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 = JPAKEUtil.CalculateGA(p, gx3, gx1, gx2);
+            this.b = round2PayloadReceived.A;
+            BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s;
+
+            JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId);
+            JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId);
+            JPAKEUtil.ValidateGa(gB);
+            JPAKEUtil.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest);
+
+            this.state = STATE_ROUND_2_VALIDATED;
+        }
+
+        /**
+         * 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 {@link JPAKEParticipant}).
+         * <p>
+         * 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 {@link JPAKEParticipant} for details on how to execute
+         * rounds 3 and 4.
+         * <p>
+         * The keying material will be in the range <tt>[0, p-1]</tt>.
+         * <p>
+         * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method.
+         * <p>
+         * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_KEY_CALCULATED}.
+         *
+         * @throws InvalidOperationException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)},
+         * or if called multiple times.
+         */
+        public 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 = JPAKEUtil.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 = JPAKEUtil.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;
+        }
+
+        /**
+         * Creates and returns the payload to send to the other participant during round 3.
+         * <p>
+         * See {@link JPAKEParticipant} for more details on round 3.
+         * <p>
+         * After execution, the {@link #getState() state} will be  {@link #STATE_ROUND_3_CREATED}.
+         *
+         * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+         * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+         */
+        public 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 = JPAKEUtil.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);
+        }
+
+        /**
+         * Validates the payload received from the other participant during round 3.
+         * <p>
+         * See {@link JPAKEParticipant} for more details on round 3.
+         * <p>
+         * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}.
+         *
+         * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}.
+         * @throws CryptoException if validation fails.
+         * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times
+         */
+        public 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);
+            }
+
+            JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId);
+            JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId);
+
+            JPAKEUtil.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..1b0c514bb
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs
@@ -0,0 +1,117 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Agreement.Jpake
+{
+    /**
+     * A pre-computed prime order group for use during a J-PAKE exchange.
+     * <p>
+     * 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.
+     * <p>
+     * See {@link JPAKEPrimeOrderGroups} for convenient standard groups.
+     * <p>
+     * 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.
+     */
+    public class JPAKEPrimeOrderGroup
+    {
+        private readonly BigInteger p;
+        private readonly BigInteger q;
+        private readonly BigInteger g;
+
+        /**
+         * Constructs a new {@link JPAKEPrimeOrderGroup}.
+         * <p>
+         * In general, you should use one of the pre-approved groups from
+         * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one.
+         * <p>
+         * The following basic checks are performed:
+         * <ul>
+         * <li>p-1 must be evenly divisible by q</li>
+         * <li>g must be in [2, p-1]</li>
+         * <li>g^q mod p must equal 1</li>
+         * <li>p must be prime (within reasonably certainty)</li>
+         * <li>q must be prime (within reasonably certainty)</li>
+         * </ul>
+         * <p>
+         * The prime checks are performed using {@link BigInteger#isProbablePrime(int)},
+         * and are therefore subject to the same probability guarantees.
+         * <p>
+         * 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 if any of the above validations fail
+         */
+        public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g)
+            : this(p, q, g, false)
+        {
+            /*
+             * Don't skip the checks on user-specified groups.
+             */
+        }
+
+        /**
+         * Internal package-private constructor used by the pre-approved
+         * groups in {@link JPAKEPrimeOrderGroups}.
+         * These pre-approved groups can avoid the expensive checks.
+         */
+        public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks)
+        {
+            JPAKEUtil.ValidateNotNull(p, "p");
+            JPAKEUtil.ValidateNotNull(q, "q");
+            JPAKEUtil.ValidateNotNull(g, "g");
+
+            if (!skipChecks)
+            {
+                if (!p.Subtract(JPAKEUtil.ONE).Mod(q).Equals(JPAKEUtil.ZERO))
+                {
+                    throw new ArgumentException("p-1 must be evenly divisible by q");
+                }
+                if (g.CompareTo(BigInteger.ValueOf(2)) == -1 || g.CompareTo(p.Subtract(JPAKEUtil.ONE)) == 1)
+                {
+                    throw new ArgumentException("g must be in [2, p-1]");
+                }
+                if (!g.ModPow(q, p).Equals(JPAKEUtil.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 BigInteger P
+        {
+            get { return p; }
+        }
+
+        public BigInteger Q
+        {
+            get { return q; }
+        }
+
+        public 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..aeaff6f72
--- /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..7b638dabd
--- /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)
+        {
+            JPAKEUtil.ValidateNotNull(participantId, "participantId");
+            JPAKEUtil.ValidateNotNull(gx1, "gx1");
+            JPAKEUtil.ValidateNotNull(gx2, "gx2");
+            JPAKEUtil.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1");
+            JPAKEUtil.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 string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public BigInteger Gx1
+        {
+            get { return gx1; }
+        }
+
+        public BigInteger Gx2
+        {
+            get { return gx2; }
+        }
+
+        public BigInteger[] KnowledgeProofForX1
+        {
+            get
+            {
+                BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length];
+                Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length);
+                return kp;
+            }
+        }
+
+        public 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..cf1a8575e
--- /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)
+        {
+            JPAKEUtil.ValidateNotNull(participantId, "participantId");
+            JPAKEUtil.ValidateNotNull(a, "a");
+            JPAKEUtil.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s");
+
+            this.participantId = participantId;
+            this.a = a;
+            this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length];
+            knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0);
+        }
+
+        public string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public BigInteger A
+        {
+            get { return a; }
+        }
+
+        public 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..95e0f24ce
--- /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 string ParticipantId
+        {
+            get { return participantId; }
+        }
+
+        public BigInteger MacTag
+        {
+            get { return macTag; }
+        }
+    }
+}
diff --git a/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs b/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs
new file mode 100755
index 000000000..34f8c2685
--- /dev/null
+++ b/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs
@@ -0,0 +1,404 @@
+using System;
+using System.Text;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+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 JPAKEUtil.
+    /// 
+    /// The primitives, however, can be used without a JPAKEParticipant if needed.
+    /// </summary>
+    public class JPAKEUtil
+    {
+        public static BigInteger ZERO = BigInteger.ValueOf(0);
+        public static BigInteger ONE = BigInteger.ValueOf(1);
+
+        /// <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(Strings.ToUtf8ByteArray(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)
+        {
+            BigInteger[] zeroKnowledgeProof = new BigInteger[2];
+
+            /* 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
+
+            zeroKnowledgeProof[0] = gv;
+            zeroKnowledgeProof[1] = v.Subtract(x.Multiply(h)).Mod(q); // r = v-x*h
+
+            return zeroKnowledgeProof;
+        }
+
+        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 = new byte[digest.GetDigestSize()];
+            digest.DoFinal(output, 0);
+
+            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 NullReferenceException(description + " must not be null");
+            }
+        }
+
+        /// <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);
+            byte[] macOutput = new byte[mac.GetMacSize()];
+            mac.Init(new KeyParameter(macKey));
+
+            /*
+             * 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);
+
+            mac.DoFinal(macOutput, 0);
+
+            Arrays.Fill(macKey, (byte)0);
+
+            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");
+
+            byte[] output = new byte[digest.GetDigestSize()];
+            digest.DoFinal(output, 0);
+
+            return output;
+        }
+
+        /// <summary>
+        /// Validates the MacTag received from the partner participant.
+        /// 
+        /// throws CryptoException if the participantId strings are equal.
+        /// </summary>
+        /// <param name="partnerMacTag">the MacTag received from the partner</param>
+        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)
+        {
+            byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger);
+            digest.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger)
+        {
+            byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger);
+            digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4);
+            digest.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static void UpdateDigest(IDigest digest, string str)
+        {
+            byte[] byteArray = Encoding.UTF8.GetBytes(str);
+            digest.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static void UpdateDigestIncludingSize(IDigest digest, string str)
+        {
+            byte[] byteArray = Encoding.UTF8.GetBytes(str);
+            digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4);
+            digest.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static void UpdateMac(IMac mac, BigInteger bigInteger)
+        {
+            byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger);
+            mac.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static void UpdateMac(IMac mac, string str)
+        {
+            byte[] byteArray = Encoding.UTF8.GetBytes(str);
+            mac.BlockUpdate(byteArray, 0, byteArray.Length);
+            Arrays.Fill(byteArray, (byte)0);
+        }
+
+        private static byte[] IntToByteArray(int value)
+        {
+            return new byte[]{
+                (byte)((uint)value >> 24),
+                (byte)((uint)value >> 16),
+                (byte)((uint)value >> 8),
+                (byte)value
+            };
+        }
+
+    }
+}
diff --git a/crypto/test/src/crypto/test/JPAKEParticipantTest.cs b/crypto/test/src/crypto/test/JPAKEParticipantTest.cs
new file mode 100644
index 000000000..7c01bf237
--- /dev/null
+++ b/crypto/test/src/crypto/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.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 (NullReferenceException)
+            {
+                // expected
+            }
+
+            // null password
+            try
+            {
+                new JPAKEParticipant(participantId, null, group, digest, random);
+
+                Fail("failed to throw exception on null password");
+            }
+            catch (NullReferenceException)
+            {
+                // 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 (NullReferenceException)
+            {
+                // expected
+            }
+
+            // null digest
+            try
+            {
+                new JPAKEParticipant(participantId, password, group, null, random);
+
+                Fail("failed to throw exception on null digest");
+            }
+            catch (NullReferenceException)
+            {
+                // expected
+            }
+
+            // null random
+            try
+            {
+                new JPAKEParticipant(participantId, password, group, digest, null);
+
+                Fail("failed to throw exception on null random");
+            }
+            catch (NullReferenceException)
+            {
+                // 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/test/JPAKEPrimeOrderGroupTest.cs b/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs
new file mode 100644
index 000000000..d9749cb53
--- /dev/null
+++ b/crypto/test/src/crypto/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.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/test/JPAKEUtilTest.cs b/crypto/test/src/crypto/test/JPAKEUtilTest.cs
new file mode 100644
index 000000000..7ac54aba0
--- /dev/null
+++ b/crypto/test/src/crypto/test/JPAKEUtilTest.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.Tests
+{
+    [TestFixture]
+    public class JPAKEUtilTest
+        : 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 "JPAKEUtil"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new JPAKEUtilTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+
+        public void TestValidateGx4()
+        {
+            JPAKEUtil.ValidateGx4(Ten);
+
+            try
+            {
+                JPAKEUtil.ValidateGx4(BigInteger.One);
+
+                Fail("exception not thrown for g^x4 equal to 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateGa()
+        {
+            JPAKEUtil.ValidateGa(Ten);
+
+            try
+            {
+                JPAKEUtil.ValidateGa(BigInteger.One);
+
+                Fail("exception not thrown for g^a equal to 1");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateParticipantIdsDiffer()
+        {
+            JPAKEUtil.ValidateParticipantIdsDiffer("a", "b");
+            JPAKEUtil.ValidateParticipantIdsDiffer("a", "A");
+
+            try
+            {
+                JPAKEUtil.ValidateParticipantIdsDiffer("a", "a");
+
+                Fail("validate participant ids differ not throwing exception for equal participant ids");
+            }
+            catch (CryptoException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateParticipantsIdsEqual()
+        {
+            JPAKEUtil.ValidateParticipantIdsEqual("a", "a");
+
+            try
+            {
+                JPAKEUtil.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 = JPAKEUtil.GenerateX1(pg1.Q, random);
+            BigInteger x2 = JPAKEUtil.GenerateX2(pg1.Q, random);
+            BigInteger x3 = JPAKEUtil.GenerateX1(pg1.Q, random);
+            BigInteger x4 = JPAKEUtil.GenerateX2(pg1.Q, random);
+
+            BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1);
+            BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2);
+            BigInteger gx3 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x3);
+            BigInteger gx4 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x4);
+
+            BigInteger gB = JPAKEUtil.CalculateGA(pg1.P, gx3, gx1, gx2);
+
+            BigInteger s = JPAKEUtil.CalculateS("password".ToCharArray());
+
+            BigInteger xs = JPAKEUtil.CalculateX2s(pg1.Q, x4, s);
+
+            BigInteger B = JPAKEUtil.CalculateA(pg1.P, pg1.Q, gB, xs);
+
+            BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(pg1.P, pg1.Q, gx4, x2, s, B);
+
+            BigInteger macTag = JPAKEUtil.CalculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest);
+
+            // should succeed
+            JPAKEUtil.ValidateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag);
+
+            // validating own macTag (as opposed to the other party's mactag)
+            try
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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()
+        {
+            JPAKEUtil.ValidateNotNull("a", "description");
+
+            try
+            {
+                JPAKEUtil.ValidateNotNull(null, "description");
+
+                Fail("failed to throw exception on null");
+            }
+            catch (NullReferenceException)
+            {
+                // expected
+            }
+        }
+
+        public void TestValidateZeroKnowledgeProof()
+        {
+            JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024;
+
+            SecureRandom random = new SecureRandom();
+            IDigest digest1 = new Sha256Digest();
+
+            BigInteger x1 = JPAKEUtil.GenerateX1(pg1.Q, random);
+            BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1);
+            string participantId1 = "participant1";
+
+            BigInteger[] zkp1 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, x1, participantId1, digest1, random);
+
+            // should succeed
+            JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest1);
+
+            // wrong group
+            JPAKEPrimeOrderGroup pg2 = JPAKEPrimeOrderGroups.NIST_3072;
+            try
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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 = JPAKEUtil.GenerateX2(pg1.Q, random);
+            BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2);
+            try
+            {
+                JPAKEUtil.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 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, x2, participantId1, digest1, random);
+            try
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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
+            {
+                JPAKEUtil.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
+            }
+        }
+    }
+}