/// <summary>
        /// Creates and returns the payload to send to the other participant during round 3.
        ///
        /// See JPakeParticipant for more details on round 3.
        ///
        /// After execution, the State state} will be  STATE_ROUND_3_CREATED.
        /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple
        /// times.
        /// </summary>
        /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
        public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial)
        {
            if (this.state >= STATE_ROUND_3_CREATED)
            {
                throw new InvalidOperationException("Round 3 payload already created for " + this.participantId);
            }
            if (this.state < STATE_KEY_CALCULATED)
            {
                throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId);
            }

            BigInteger macTag = JPakeUtilities.CalculateMacTag(
                this.participantId,
                this.partnerParticipantId,
                this.gx1,
                this.gx2,
                this.gx3,
                this.gx4,
                keyingMaterial,
                this.digest);

            this.state = STATE_ROUND_3_CREATED;

            return(new JPakeRound3Payload(participantId, macTag));
        }
        /// <summary>
        /// Validates the payload received from the other participant during round 3.
        ///
        /// See JPakeParticipant for more details on round 3.
        ///
        /// After execution, the State state will be STATE_ROUND_3_VALIDATED.
        ///
        /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to
        /// CalculateKeyingMaterial or multiple times
        /// </summary>
        /// <param name="round3PayloadReceived">The round 3 payload received from the other participant.</param>
        /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
        public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial)
        {
            if (this.state >= STATE_ROUND_3_VALIDATED)
            {
                throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId);
            }
            if (this.state < STATE_KEY_CALCULATED)
            {
                throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId);
            }

            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId);
            JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId);

            JPakeUtilities.ValidateMacTag(
                this.participantId,
                this.partnerParticipantId,
                this.gx1,
                this.gx2,
                this.gx3,
                this.gx4,
                keyingMaterial,
                this.digest,
                round3PayloadReceived.MacTag);

            // Clear the rest of the fields.
            this.gx1 = null;
            this.gx2 = null;
            this.gx3 = null;
            this.gx4 = null;

            this.state = STATE_ROUND_3_VALIDATED;
        }
Example #3
0
        public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s)
        {
            JPakeUtilities.ValidateNotNull(participantId, "participantId");
            JPakeUtilities.ValidateNotNull(a, "a");
            JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s");

            this.participantId        = participantId;
            this.a                    = a;
            this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length];
            knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0);
        }
Example #4
0
        public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2)
        {
            JPakeUtilities.ValidateNotNull(participantId, "participantId");
            JPakeUtilities.ValidateNotNull(gx1, "gx1");
            JPakeUtilities.ValidateNotNull(gx2, "gx2");
            JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1");
            JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2");

            this.participantId       = participantId;
            this.gx1                 = gx1;
            this.gx2                 = gx2;
            this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length];
            Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length);
            this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length];
            Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length);
        }
        /// <summary>
        /// Creates and returns the payload to send to the other participant during round 1.
        ///
        /// After execution, the State state} will be STATE_ROUND_1_CREATED}.
        /// </summary>
        public virtual JPakeRound1Payload CreateRound1PayloadToSend()
        {
            if (this.state >= STATE_ROUND_1_CREATED)
            {
                throw new InvalidOperationException("Round 1 payload already created for " + this.participantId);
            }

            this.x1 = JPakeUtilities.GenerateX1(q, random);
            this.x2 = JPakeUtilities.GenerateX2(q, random);

            this.gx1 = JPakeUtilities.CalculateGx(p, g, x1);
            this.gx2 = JPakeUtilities.CalculateGx(p, g, x2);
            BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
            BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);

            this.state = STATE_ROUND_1_CREATED;

            return(new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2));
        }
        /// <summary>
        /// Validates the payload received from the other participant during round 1.
        ///
        /// Must be called prior to CreateRound2PayloadToSend().
        ///
        /// After execution, the State state will be  STATE_ROUND_1_VALIDATED.
        ///
        /// Throws CryptoException if validation fails. Throws InvalidOperationException
        /// if called multiple times.
        /// </summary>
        public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived)
        {
            if (this.state >= STATE_ROUND_1_VALIDATED)
            {
                throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId);
            }

            this.partnerParticipantId = round1PayloadReceived.ParticipantId;
            this.gx3 = round1PayloadReceived.Gx1;
            this.gx4 = round1PayloadReceived.Gx2;

            BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1;
            BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2;

            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId);
            JPakeUtilities.ValidateGx4(gx4);
            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest);
            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest);
            this.state = STATE_ROUND_1_VALIDATED;
        }
        /// <summary>
        /// Creates and returns the payload to send to the other participant during round 2.
        ///
        /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method.
        ///
        /// After execution, the State state will be  STATE_ROUND_2_CREATED.
        ///
        /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
        /// </summary>
        public virtual JPakeRound2Payload CreateRound2PayloadToSend()
        {
            if (this.state >= STATE_ROUND_2_CREATED)
            {
                throw new InvalidOperationException("Round 2 payload already created for " + this.participantId);
            }
            if (this.state < STATE_ROUND_1_VALIDATED)
            {
                throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId);
            }

            BigInteger gA  = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4);
            BigInteger s   = JPakeUtilities.CalculateS(password);
            BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s);
            BigInteger A   = JPakeUtilities.CalculateA(p, q, gA, x2s);

            BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);

            this.state = STATE_ROUND_2_CREATED;

            return(new JPakeRound2Payload(participantId, A, knowledgeProofForX2s));
        }
        /// <summary>
        /// Validates the payload received from the other participant during round 2.
        /// Note that this DOES NOT detect a non-common password.
        /// The only indication of a non-common password is through derivation
        /// of different keys (which can be detected explicitly by executing round 3 and round 4)
        ///
        /// Must be called prior to CalculateKeyingMaterial().
        ///
        /// After execution, the State state will be STATE_ROUND_2_VALIDATED.
        ///
        /// Throws CryptoException if validation fails. Throws
        /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
        /// </summary>
        public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived)
        {
            if (this.state >= STATE_ROUND_2_VALIDATED)
            {
                throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId);
            }
            if (this.state < STATE_ROUND_1_VALIDATED)
            {
                throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId);
            }

            BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2);

            this.b = round2PayloadReceived.A;
            BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s;

            JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId);
            JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId);
            JPakeUtilities.ValidateGa(gB);
            JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest);

            this.state = STATE_ROUND_2_VALIDATED;
        }
        /// <summary>
        /// Constructor for a new JPakeParticipant.
        ///
        /// After construction, the State state will be STATE_INITIALIZED.
        ///
        /// Throws NullReferenceException if any argument is null. Throws
        /// ArgumentException if password is empty.
        /// </summary>
        /// <param name="participantId">Unique identifier of this participant.
        ///      The two participants in the exchange must NOT share the same id.</param>
        /// <param name="password">Shared secret.
        ///      A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
        ///      Caller should clear the input password as soon as possible.</param>
        /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
        /// <param name="digest">Digest to use during zero knowledge proofs and key confirmation
        ///     (SHA-256 or stronger preferred).</param>
        /// <param name="random">Source of secure random data for x1 and x2, and for the zero knowledge proofs.</param>
        public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random)
        {
            JPakeUtilities.ValidateNotNull(participantId, "participantId");
            JPakeUtilities.ValidateNotNull(password, "password");
            JPakeUtilities.ValidateNotNull(group, "p");
            JPakeUtilities.ValidateNotNull(digest, "digest");
            JPakeUtilities.ValidateNotNull(random, "random");

            if (password.Length == 0)
            {
                throw new ArgumentException("Password must not be empty.");
            }

            this.participantId = participantId;

            // Create a defensive copy so as to fully encapsulate the password.
            //
            // This array will contain the password for the lifetime of this
            // participant BEFORE CalculateKeyingMaterial() is called.
            //
            // i.e. When CalculateKeyingMaterial() is called, the array will be cleared
            // in order to remove the password from memory.
            //
            // The caller is responsible for clearing the original password array
            // given as input to this constructor.
            this.password = new char[password.Length];
            Array.Copy(password, this.password, password.Length);

            this.p = group.P;
            this.q = group.Q;
            this.g = group.G;

            this.digest = digest;
            this.random = random;

            this.state = STATE_INITIALIZED;
        }
        /// <summary>
        /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups.
        /// These pre-approved groups can avoid the expensive checks.
        /// User-specified groups should not use this constructor.
        /// </summary>
        public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks)
        {
            JPakeUtilities.ValidateNotNull(p, "p");
            JPakeUtilities.ValidateNotNull(q, "q");
            JPakeUtilities.ValidateNotNull(g, "g");

            if (!skipChecks)
            {
                if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero))
                {
                    throw new ArgumentException("p-1 must be evenly divisible by q");
                }
                if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1)
                {
                    throw new ArgumentException("g must be in [2, p-1]");
                }
                if (!g.ModPow(q, p).Equals(JPakeUtilities.One))
                {
                    throw new ArgumentException("g^q mod p must equal 1");
                }

                // Note these checks do not guarantee that p and q are prime.
                // We just have reasonable certainty that they are prime.
                if (!p.IsProbablePrime(20))
                {
                    throw new ArgumentException("p must be prime");
                }
                if (!q.IsProbablePrime(20))
                {
                    throw new ArgumentException("q must be prime");
                }
            }

            this.p = p;
            this.q = q;
            this.g = g;
        }
Example #11
0
        /// <summary>
        /// Calculates and returns the key material.
        /// A session key must be derived from this key material using a secure key derivation function (KDF).
        /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant).
        ///
        /// The keying material will be identical for each participant if and only if
        /// each participant's password is the same.  i.e. If the participants do not
        /// share the same password, then each participant will derive a different key.
        /// Therefore, if you immediately start using a key derived from
        /// the keying material, then you must handle detection of incorrect keys.
        /// If you want to handle this detection explicitly, you can optionally perform
        /// rounds 3 and 4.  See JPakeParticipant for details on how to execute
        /// rounds 3 and 4.
        ///
        /// The keying material will be in the range <tt>[0, p-1]</tt>.
        ///
        /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method.
        ///
        /// As a side effect, the internal password array is cleared, since it is no longer needed.
        ///
        /// After execution, the State state will be STATE_KEY_CALCULATED.
        ///
        /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload),
        /// or if called multiple times.
        /// </summary>
        public virtual BigInteger CalculateKeyingMaterial()
        {
            if (this.state >= STATE_KEY_CALCULATED)
            {
                throw new InvalidOperationException("Key already calculated for " + participantId);
            }
            if (this.state < STATE_ROUND_2_VALIDATED)
            {
                throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId);
            }

            BigInteger s = JPakeUtilities.CalculateS(password);

            // Clear the password array from memory, since we don't need it anymore.
            // Also set the field to null as a flag to indicate that the key has already been calculated.
            Array.Clear(password, 0, password.Length);
            this.password = null;

            BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b);

            // Clear the ephemeral private key fields as well.
            // Note that we're relying on the garbage collector to do its job to clean these up.
            // The old objects will hang around in memory until the garbage collector destroys them.
            //
            // If the ephemeral private keys x1 and x2 are leaked,
            // the attacker might be able to brute-force the password.
            this.x1 = null;
            this.x2 = null;
            this.b  = null;

            // Do not clear gx* yet, since those are needed by round 3.

            this.state = STATE_KEY_CALCULATED;

            return(keyingMaterial);
        }