This commit is contained in:
2026-06-15 18:18:16 +08:00
parent 97c9fba14e
commit 2b9f134e5f
4164 changed files with 386922 additions and 79 deletions

View File

@@ -0,0 +1,459 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Security;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
///
/// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper
/// <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf">
/// "Password Authenticated Key Exchange by Juggling, 2008."</a>
///
/// The J-PAKE protocol is symmetric.
/// There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>.
/// An instance of JPakeParticipant represents one participant, and
/// is the primary interface for executing the exchange.
///
/// To execute an exchange, construct a JPakeParticipant on each end,
/// and call the following 7 methods
/// (once and only once, in the given order, for each participant, sending messages between them as described):
///
/// CreateRound1PayloadToSend() - and send the payload to the other participant
/// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant
/// CreateRound2PayloadToSend() - and send the payload to the other participant
/// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant
/// CalculateKeyingMaterial()
/// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant
/// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant
///
/// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial().
/// The caller is responsible for deriving the session key using a secure key derivation function (KDF).
///
/// Round 3 is an optional key confirmation process.
/// If you do not execute round 3, then there is no assurance that both participants are using the same key.
/// (i.e. if the participants used different passwords, then their session keys will differ.)
///
/// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
///
/// The symmetric design can easily support the asymmetric cases when one party initiates the communication.
/// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass.
/// Also, in some cases, the key confirmation payload can be sent together with the round2 payload.
/// These are the trivial techniques to optimize the communication.
///
/// The key confirmation process is implemented as specified in
/// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
/// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
///
/// This class is stateful and NOT threadsafe.
/// Each instance should only be used for ONE complete J-PAKE exchange
/// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange).
/// </summary>
public class JPakeParticipant
{
// Possible internal states. Used for state checking.
public static readonly int STATE_INITIALIZED = 0;
public static readonly int STATE_ROUND_1_CREATED = 10;
public static readonly int STATE_ROUND_1_VALIDATED = 20;
public static readonly int STATE_ROUND_2_CREATED = 30;
public static readonly int STATE_ROUND_2_VALIDATED = 40;
public static readonly int STATE_KEY_CALCULATED = 50;
public static readonly int STATE_ROUND_3_CREATED = 60;
public static readonly int STATE_ROUND_3_VALIDATED = 70;
// Unique identifier of this participant.
// The two participants in the exchange must NOT share the same id.
private string participantId;
// Shared secret. This only contains the secret between construction
// and the call to CalculateKeyingMaterial().
//
// i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's,
// and the field is set to null.
private char[] password;
// Digest to use during calculations.
private IDigest digest;
// Source of secure random data.
private readonly SecureRandom random;
private readonly BigInteger p;
private readonly BigInteger q;
private readonly BigInteger g;
// The participantId of the other participant in this exchange.
private string partnerParticipantId;
// Alice's x1 or Bob's x3.
private BigInteger x1;
// Alice's x2 or Bob's x4.
private BigInteger x2;
// Alice's g^x1 or Bob's g^x3.
private BigInteger gx1;
// Alice's g^x2 or Bob's g^x4.
private BigInteger gx2;
// Alice's g^x3 or Bob's g^x1.
private BigInteger gx3;
// Alice's g^x4 or Bob's g^x2.
private BigInteger gx4;
// Alice's B or Bob's A.
private BigInteger b;
// The current state.
// See the <tt>STATE_*</tt> constants for possible values.
private int state;
/// <summary>
/// Convenience constructor for a new JPakeParticipant that uses
/// the JPakePrimeOrderGroups#NIST_3072 prime order group,
/// a SHA-256 digest, and a default SecureRandom implementation.
///
/// After construction, the State state will be STATE_INITIALIZED.
///
/// Throws NullReferenceException if any argument is null. Throws
/// ArgumentException if password is empty.
/// </summary>
/// <param name="participantId">Unique identifier of this participant.
/// The two participants in the exchange must NOT share the same id.</param>
/// <param name="password">Shared secret.
/// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
/// Caller should clear the input password as soon as possible.</param>
public JPakeParticipant(string participantId, char[] password)
: this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { }
/// <summary>
/// Convenience constructor for a new JPakeParticipant that uses
/// a SHA-256 digest, and a default SecureRandom implementation.
///
/// After construction, the State state will be STATE_INITIALIZED.
///
/// Throws NullReferenceException if any argument is null. Throws
/// ArgumentException if password is empty.
/// </summary>
/// <param name="participantId">Unique identifier of this participant.
/// The two participants in the exchange must NOT share the same id.</param>
/// <param name="password">Shared secret.
/// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
/// Caller should clear the input password as soon as possible.</param>
/// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group)
: this(participantId, password, group, new Sha256Digest(), CryptoServicesRegistrar.GetSecureRandom()) { }
/// <summary>
/// Constructor for a new JPakeParticipant.
///
/// After construction, the State state will be STATE_INITIALIZED.
///
/// Throws NullReferenceException if any argument is null. Throws
/// ArgumentException if password is empty.
/// </summary>
/// <param name="participantId">Unique identifier of this participant.
/// The two participants in the exchange must NOT share the same id.</param>
/// <param name="password">Shared secret.
/// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
/// Caller should clear the input password as soon as possible.</param>
/// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
/// <param name="digest">Digest to use during zero knowledge proofs and key confirmation
/// (SHA-256 or stronger preferred).</param>
/// <param name="random">Source of secure random data for x1 and x2, and for the zero knowledge proofs.</param>
public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest,
SecureRandom random)
{
JPakeUtilities.ValidateNotNull(participantId, "participantId");
JPakeUtilities.ValidateNotNull(password, "password");
JPakeUtilities.ValidateNotNull(group, "p");
JPakeUtilities.ValidateNotNull(digest, "digest");
JPakeUtilities.ValidateNotNull(random, "random");
if (password.Length == 0)
throw new ArgumentException("Password must not be empty.");
this.participantId = participantId;
// Create a defensive copy so as to fully encapsulate the password.
//
// This array will contain the password for the lifetime of this
// participant BEFORE CalculateKeyingMaterial() is called.
//
// i.e. When CalculateKeyingMaterial() is called, the array will be cleared
// in order to remove the password from memory.
//
// The caller is responsible for clearing the original password array
// given as input to this constructor.
this.password = new char[password.Length];
Array.Copy(password, this.password, password.Length);
this.p = group.P;
this.q = group.Q;
this.g = group.G;
this.digest = digest;
this.random = random;
this.state = STATE_INITIALIZED;
}
/// <summary>
/// Gets the current state of this participant.
/// See the <tt>STATE_*</tt> constants for possible values.
/// </summary>
public virtual int State
{
get { return state; }
}
/// <summary>
/// Creates and returns the payload to send to the other participant during round 1.
///
/// After execution, the State state} will be STATE_ROUND_1_CREATED}.
/// </summary>
public virtual JPakeRound1Payload CreateRound1PayloadToSend()
{
if (this.state >= STATE_ROUND_1_CREATED)
throw new InvalidOperationException("Round 1 payload already created for " + this.participantId);
this.x1 = JPakeUtilities.GenerateX1(q, random);
this.x2 = JPakeUtilities.GenerateX2(q, random);
this.gx1 = JPakeUtilities.CalculateGx(p, g, x1);
this.gx2 = JPakeUtilities.CalculateGx(p, g, x2);
BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
this.state = STATE_ROUND_1_CREATED;
return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
}
/// <summary>
/// Validates the payload received from the other participant during round 1.
///
/// Must be called prior to CreateRound2PayloadToSend().
///
/// After execution, the State state will be STATE_ROUND_1_VALIDATED.
///
/// Throws CryptoException if validation fails. Throws InvalidOperationException
/// if called multiple times.
/// </summary>
public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived)
{
if (this.state >= STATE_ROUND_1_VALIDATED)
throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId);
this.partnerParticipantId = round1PayloadReceived.ParticipantId;
this.gx3 = round1PayloadReceived.Gx1;
this.gx4 = round1PayloadReceived.Gx2;
BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1;
BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2;
JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId);
JPakeUtilities.ValidateGx4(gx4);
JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest);
JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest);
this.state = STATE_ROUND_1_VALIDATED;
}
/// <summary>
/// Creates and returns the payload to send to the other participant during round 2.
///
/// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method.
///
/// After execution, the State state will be STATE_ROUND_2_CREATED.
///
/// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
/// </summary>
public virtual JPakeRound2Payload CreateRound2PayloadToSend()
{
if (this.state >= STATE_ROUND_2_CREATED)
throw new InvalidOperationException("Round 2 payload already created for " + this.participantId);
if (this.state < STATE_ROUND_1_VALIDATED)
throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId);
BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4);
BigInteger s = JPakeUtilities.CalculateS(password);
BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s);
BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s);
BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
this.state = STATE_ROUND_2_CREATED;
return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s);
}
/// <summary>
/// Validates the payload received from the other participant during round 2.
/// Note that this DOES NOT detect a non-common password.
/// The only indication of a non-common password is through derivation
/// of different keys (which can be detected explicitly by executing round 3 and round 4)
///
/// Must be called prior to CalculateKeyingMaterial().
///
/// After execution, the State state will be STATE_ROUND_2_VALIDATED.
///
/// Throws CryptoException if validation fails. Throws
/// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
/// </summary>
public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived)
{
if (this.state >= STATE_ROUND_2_VALIDATED)
throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId);
if (this.state < STATE_ROUND_1_VALIDATED)
throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId);
BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2);
this.b = round2PayloadReceived.A;
BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s;
JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId);
JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId);
JPakeUtilities.ValidateGa(gB);
JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest);
this.state = STATE_ROUND_2_VALIDATED;
}
/// <summary>
/// Calculates and returns the key material.
/// A session key must be derived from this key material using a secure key derivation function (KDF).
/// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant).
///
/// The keying material will be identical for each participant if and only if
/// each participant's password is the same. i.e. If the participants do not
/// share the same password, then each participant will derive a different key.
/// Therefore, if you immediately start using a key derived from
/// the keying material, then you must handle detection of incorrect keys.
/// If you want to handle this detection explicitly, you can optionally perform
/// rounds 3 and 4. See JPakeParticipant for details on how to execute
/// rounds 3 and 4.
///
/// The keying material will be in the range <tt>[0, p-1]</tt>.
///
/// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method.
///
/// As a side effect, the internal password array is cleared, since it is no longer needed.
///
/// After execution, the State state will be STATE_KEY_CALCULATED.
///
/// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload),
/// or if called multiple times.
/// </summary>
public virtual BigInteger CalculateKeyingMaterial()
{
if (this.state >= STATE_KEY_CALCULATED)
throw new InvalidOperationException("Key already calculated for " + participantId);
if (this.state < STATE_ROUND_2_VALIDATED)
throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId);
BigInteger s = JPakeUtilities.CalculateS(password);
// Clear the password array from memory, since we don't need it anymore.
// Also set the field to null as a flag to indicate that the key has already been calculated.
Array.Clear(password, 0, password.Length);
this.password = null;
BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b);
// Clear the ephemeral private key fields as well.
// Note that we're relying on the garbage collector to do its job to clean these up.
// The old objects will hang around in memory until the garbage collector destroys them.
//
// If the ephemeral private keys x1 and x2 are leaked,
// the attacker might be able to brute-force the password.
this.x1 = null;
this.x2 = null;
this.b = null;
// Do not clear gx* yet, since those are needed by round 3.
this.state = STATE_KEY_CALCULATED;
return keyingMaterial;
}
/// <summary>
/// Creates and returns the payload to send to the other participant during round 3.
///
/// See JPakeParticipant for more details on round 3.
///
/// After execution, the State state} will be STATE_ROUND_3_CREATED.
/// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple
/// times.
/// </summary>
/// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial)
{
if (this.state >= STATE_ROUND_3_CREATED)
throw new InvalidOperationException("Round 3 payload already created for " + this.participantId);
if (this.state < STATE_KEY_CALCULATED)
throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId);
BigInteger macTag = JPakeUtilities.CalculateMacTag(
this.participantId,
this.partnerParticipantId,
this.gx1,
this.gx2,
this.gx3,
this.gx4,
keyingMaterial,
this.digest);
this.state = STATE_ROUND_3_CREATED;
return new JPakeRound3Payload(participantId, macTag);
}
/// <summary>
/// Validates the payload received from the other participant during round 3.
///
/// See JPakeParticipant for more details on round 3.
///
/// After execution, the State state will be STATE_ROUND_3_VALIDATED.
///
/// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to
/// CalculateKeyingMaterial or multiple times
/// </summary>
/// <param name="round3PayloadReceived">The round 3 payload received from the other participant.</param>
/// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial)
{
if (this.state >= STATE_ROUND_3_VALIDATED)
throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId);
if (this.state < STATE_KEY_CALCULATED)
throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId);
JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId);
JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId);
JPakeUtilities.ValidateMacTag(
this.participantId,
this.partnerParticipantId,
this.gx1,
this.gx2,
this.gx3,
this.gx4,
keyingMaterial,
this.digest,
round3PayloadReceived.MacTag);
// Clear the rest of the fields.
this.gx1 = null;
this.gx2 = null;
this.gx3 = null;
this.gx4 = null;
this.state = STATE_ROUND_3_VALIDATED;
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a44e1a709319484fbb595c41ceae6f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// A pre-computed prime order group for use during a J-PAKE exchange.
///
/// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group
/// that is suitable for public key cryptography, including elliptic curve cryptography.
///
/// See JPakePrimeOrderGroups for convenient standard groups.
///
/// NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
/// many groups that can be used for the desired level of security.
/// </summary>
public class JPakePrimeOrderGroup
{
private readonly BigInteger p;
private readonly BigInteger q;
private readonly BigInteger g;
/// <summary>
/// Constructs a new JPakePrimeOrderGroup.
///
/// In general, you should use one of the pre-approved groups from
/// JPakePrimeOrderGroups, rather than manually constructing one.
///
/// The following basic checks are performed:
///
/// p-1 must be evenly divisible by q
/// g must be in [2, p-1]
/// g^q mod p must equal 1
/// p must be prime (within reasonably certainty)
/// q must be prime (within reasonably certainty)
///
/// The prime checks are performed using BigInteger#isProbablePrime(int),
/// and are therefore subject to the same probability guarantees.
///
/// These checks prevent trivial mistakes.
/// However, due to the small uncertainties if p and q are not prime,
/// advanced attacks are not prevented.
/// Use it at your own risk.
///
/// Throws NullReferenceException if any argument is null. Throws
/// InvalidOperationException is any of the above validations fail.
/// </summary>
public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g)
: this(p, q, g, false)
{
// Don't skip the checks on user-specified groups.
}
/// <summary>
/// Constructor used by the pre-approved groups in JPakePrimeOrderGroups.
/// These pre-approved groups can avoid the expensive checks.
/// User-specified groups should not use this constructor.
/// </summary>
public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks)
{
JPakeUtilities.ValidateNotNull(p, "p");
JPakeUtilities.ValidateNotNull(q, "q");
JPakeUtilities.ValidateNotNull(g, "g");
if (!skipChecks)
{
if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero))
throw new ArgumentException("p-1 must be evenly divisible by q");
if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1)
throw new ArgumentException("g must be in [2, p-1]");
if (!g.ModPow(q, p).Equals(JPakeUtilities.One))
throw new ArgumentException("g^q mod p must equal 1");
// Note these checks do not guarantee that p and q are prime.
// We just have reasonable certainty that they are prime.
if (!p.IsProbablePrime(20))
throw new ArgumentException("p must be prime");
if (!q.IsProbablePrime(20))
throw new ArgumentException("q must be prime");
}
this.p = p;
this.q = q;
this.g = g;
}
public virtual BigInteger P
{
get { return p; }
}
public virtual BigInteger Q
{
get { return q; }
}
public virtual BigInteger G
{
get { return g; }
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f3a2f982f4c3de4dabcd30a73afb055
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,112 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
namespace Best.HTTP.SecureProtocol.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
);
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bbd8030d7e46ed341af2da2d455f1725
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// The payload sent/received during the first round of a J-PAKE exchange.
///
/// Each JPAKEParticipant creates and sends an instance of this payload to
/// the other. The payload to send should be created via
/// JPAKEParticipant.CreateRound1PayloadToSend().
///
/// Each participant must also validate the payload received from the other.
/// The received payload should be validated via
/// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload).
/// </summary>
public class JPakeRound1Payload
{
/// <summary>
/// The id of the JPAKEParticipant who created/sent this payload.
/// </summary>
private readonly string participantId;
/// <summary>
/// The value of g^x1
/// </summary>
private readonly BigInteger gx1;
/// <summary>
/// The value of g^x2
/// </summary>
private readonly BigInteger gx2;
/// <summary>
/// The zero knowledge proof for x1.
///
/// This is a two element array, containing {g^v, r} for x1.
/// </summary>
private readonly BigInteger[] knowledgeProofForX1;
/// <summary>
/// The zero knowledge proof for x2.
///
/// This is a two element array, containing {g^v, r} for x2.
/// </summary>
private readonly BigInteger[] knowledgeProofForX2;
public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2)
{
JPakeUtilities.ValidateNotNull(participantId, "participantId");
JPakeUtilities.ValidateNotNull(gx1, "gx1");
JPakeUtilities.ValidateNotNull(gx2, "gx2");
JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1");
JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2");
this.participantId = participantId;
this.gx1 = gx1;
this.gx2 = gx2;
this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length];
Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length);
this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length];
Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length);
}
public virtual string ParticipantId
{
get { return participantId; }
}
public virtual BigInteger Gx1
{
get { return gx1; }
}
public virtual BigInteger Gx2
{
get { return gx2; }
}
public virtual BigInteger[] KnowledgeProofForX1
{
get
{
BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length];
Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length);
return kp;
}
}
public virtual BigInteger[] KnowledgeProofForX2
{
get
{
BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length];
Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length);
return kp;
}
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa863481cc9acaa46adf027ef83a163c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,76 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// The payload sent/received during the second round of a J-PAKE exchange.
///
/// Each JPAKEParticipant creates and sends an instance
/// of this payload to the other JPAKEParticipant.
/// The payload to send should be created via
/// JPAKEParticipant#createRound2PayloadToSend()
///
/// Each JPAKEParticipant must also validate the payload
/// received from the other JPAKEParticipant.
/// The received payload should be validated via
/// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload)
/// </summary>
public class JPakeRound2Payload
{
/// <summary>
/// The id of the JPAKEParticipant who created/sent this payload.
/// </summary>
private readonly string participantId;
/// <summary>
/// The value of A, as computed during round 2.
/// </summary>
private readonly BigInteger a;
/// <summary>
/// The zero knowledge proof for x2 * s.
///
/// This is a two element array, containing {g^v, r} for x2 * s.
/// </summary>
private readonly BigInteger[] knowledgeProofForX2s;
public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s)
{
JPakeUtilities.ValidateNotNull(participantId, "participantId");
JPakeUtilities.ValidateNotNull(a, "a");
JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s");
this.participantId = participantId;
this.a = a;
this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length];
knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0);
}
public virtual string ParticipantId
{
get { return participantId; }
}
public virtual BigInteger A
{
get { return a; }
}
public virtual BigInteger[] KnowledgeProofForX2s
{
get
{
BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length];
Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length);
return kp;
}
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8402c23ea397d0458cccdd653d7dfa0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// The payload sent/received during the optional third round of a J-PAKE exchange,
/// which is for explicit key confirmation.
///
/// Each JPAKEParticipant creates and sends an instance
/// of this payload to the other JPAKEParticipant.
/// The payload to send should be created via
/// JPAKEParticipant#createRound3PayloadToSend(BigInteger)
///
/// Eeach JPAKEParticipant must also validate the payload
/// received from the other JPAKEParticipant.
/// The received payload should be validated via
/// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger)
/// </summary>
public class JPakeRound3Payload
{
/// <summary>
/// The id of the {@link JPAKEParticipant} who created/sent this payload.
/// </summary>
private readonly string participantId;
/// <summary>
/// The value of MacTag, as computed by round 3.
///
/// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest)
/// </summary>
private readonly BigInteger macTag;
public JPakeRound3Payload(string participantId, BigInteger magTag)
{
this.participantId = participantId;
this.macTag = magTag;
}
public virtual string ParticipantId
{
get { return participantId; }
}
public virtual BigInteger MacTag
{
get { return macTag; }
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f379951a5b959e540bd121ac5d44fcef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,394 @@
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using System.Text;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Math;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Security;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
{
/// <summary>
/// Primitives needed for a J-PAKE exchange.
///
/// The recommended way to perform a J-PAKE exchange is by using
/// two JPAKEParticipants. Internally, those participants
/// call these primitive operations in JPakeUtilities.
///
/// The primitives, however, can be used without a JPAKEParticipant if needed.
/// </summary>
public abstract class JPakeUtilities
{
public static readonly BigInteger Zero = BigInteger.Zero;
public static readonly BigInteger One = BigInteger.One;
/// <summary>
/// Return a value that can be used as x1 or x3 during round 1.
/// The returned value is a random value in the range [0, q-1].
/// </summary>
public static BigInteger GenerateX1(BigInteger q, SecureRandom random)
{
BigInteger min = Zero;
BigInteger max = q.Subtract(One);
return BigIntegers.CreateRandomInRange(min, max, random);
}
/// <summary>
/// Return a value that can be used as x2 or x4 during round 1.
/// The returned value is a random value in the range [1, q-1].
/// </summary>
public static BigInteger GenerateX2(BigInteger q, SecureRandom random)
{
BigInteger min = One;
BigInteger max = q.Subtract(One);
return BigIntegers.CreateRandomInRange(min, max, random);
}
/// <summary>
/// Converts the given password to a BigInteger
/// for use in arithmetic calculations.
/// </summary>
public static BigInteger CalculateS(char[] password)
{
return new BigInteger(Encoding.UTF8.GetBytes(password));
}
/// <summary>
/// Calculate g^x mod p as done in round 1.
/// </summary>
public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x)
{
return g.ModPow(x, p);
}
/// <summary>
/// Calculate ga as done in round 2.
/// </summary>
public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4)
{
// ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4
return gx1.Multiply(gx3).Multiply(gx4).Mod(p);
}
/// <summary>
/// Calculate x2 * s as done in round 2.
/// </summary>
public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s)
{
return x2.Multiply(s).Mod(q);
}
/// <summary>
/// Calculate A as done in round 2.
/// </summary>
public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s)
{
// A = ga^(x*s)
return gA.ModPow(x2s, p);
}
/// <summary>
/// Calculate a zero knowledge proof of x using Schnorr's signature.
/// The returned array has two elements {g^v, r = v-x*h} for x.
/// </summary>
public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random)
{
/* Generate a random v, and compute g^v */
BigInteger vMin = Zero;
BigInteger vMax = q.Subtract(One);
BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random);
BigInteger gv = g.ModPow(v, p);
BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h
return new BigInteger[]
{
gv,
v.Subtract(x.Multiply(h)).Mod(q) // r = v-x*h
};
}
private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx,
string participantId, IDigest digest)
{
digest.Reset();
UpdateDigestIncludingSize(digest, g);
UpdateDigestIncludingSize(digest, gr);
UpdateDigestIncludingSize(digest, gx);
UpdateDigestIncludingSize(digest, participantId);
byte[] output = DigestUtilities.DoFinal(digest);
return new BigInteger(output);
}
/// <summary>
/// Validates that g^x4 is not 1.
/// throws CryptoException if g^x4 is 1
/// </summary>
public static void ValidateGx4(BigInteger gx4)
{
if (gx4.Equals(One))
throw new CryptoException("g^x validation failed. g^x should not be 1.");
}
/// <summary>
/// Validates that ga is not 1.
///
/// As described by Feng Hao...
/// Alice could simply check ga != 1 to ensure it is a generator.
/// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks.
/// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q.
///
/// throws CryptoException if ga is 1
/// </summary>
public static void ValidateGa(BigInteger ga)
{
if (ga.Equals(One))
throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again.");
}
/// <summary>
/// Validates the zero knowledge proof (generated by
/// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom)
/// is correct.
///
/// throws CryptoException if the zero knowledge proof is not correct
/// </summary>
public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest)
{
/* sig={g^v,r} */
BigInteger gv = zeroKnowledgeProof[0];
BigInteger r = zeroKnowledgeProof[1];
BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest);
if (!(gx.CompareTo(Zero) == 1 && // g^x > 0
gx.CompareTo(p) == -1 && // g^x < p
gx.ModPow(q, p).CompareTo(One) == 0 && // g^x^q mod q = 1
/*
* Below, I took a straightforward way to compute g^r * g^x^h,
* which needs 2 exp. Using a simultaneous computation technique
* would only need 1 exp.
*/
g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h
{
throw new CryptoException("Zero-knowledge proof validation failed");
}
}
/// <summary>
/// Calculates the keying material, which can be done after round 2 has completed.
/// A session key must be derived from this key material using a secure key derivation function (KDF).
/// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant).
///
/// KeyingMaterial = (B/g^{x2*x4*s})^x2
/// </summary>
public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q,
BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B)
{
return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p);
}
/// <summary>
/// Validates that the given participant ids are not equal.
/// (For the J-PAKE exchange, each participant must use a unique id.)
///
/// Throws CryptoException if the participantId strings are equal.
/// </summary>
public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2)
{
if (participantId1.Equals(participantId2))
{
throw new CryptoException(
"Both participants are using the same participantId ("
+ participantId1
+ "). This is not allowed. "
+ "Each participant must use a unique participantId.");
}
}
/// <summary>
/// Validates that the given participant ids are equal.
/// This is used to ensure that the payloads received from
/// each round all come from the same participant.
/// </summary>
public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId)
{
if (!expectedParticipantId.Equals(actualParticipantId))
{
throw new CryptoException(
"Received payload from incorrect partner ("
+ actualParticipantId
+ "). Expected to receive payload from "
+ expectedParticipantId
+ ".");
}
}
/// <summary>
/// Validates that the given object is not null.
/// throws NullReferenceException if the object is null.
/// </summary>
/// <param name="obj">object in question</param>
/// <param name="description">name of the object (to be used in exception message)</param>
public static void ValidateNotNull(object obj, string description)
{
if (obj == null)
throw new ArgumentNullException(description);
}
/// <summary>
/// Calculates the MacTag (to be used for key confirmation), as defined by
/// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
/// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
///
/// MacTag = HMAC(MacKey, MacLen, MacData)
/// MacKey = H(K || "JPAKE_KC")
/// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
///
/// Note that both participants use "KC_1_U" because the sender of the round 3 message
/// is always the initiator for key confirmation.
///
/// HMAC = {@link HMac} used with the given {@link Digest}
/// H = The given {@link Digest}
/// MacLen = length of MacTag
/// </summary>
public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId,
BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest)
{
byte[] macKey = CalculateMacKey(keyingMaterial, digest);
HMac mac = new HMac(digest);
mac.Init(new KeyParameter(macKey));
Arrays.Fill(macKey, (byte)0);
/*
* MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4.
*/
UpdateMac(mac, "KC_1_U");
UpdateMac(mac, participantId);
UpdateMac(mac, partnerParticipantId);
UpdateMac(mac, gx1);
UpdateMac(mac, gx2);
UpdateMac(mac, gx3);
UpdateMac(mac, gx4);
byte[] macOutput = MacUtilities.DoFinal(mac);
return new BigInteger(macOutput);
}
/// <summary>
/// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation).
///
/// MacKey = H(K || "JPAKE_KC")
/// </summary>
private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest)
{
digest.Reset();
UpdateDigest(digest, keyingMaterial);
/*
* This constant is used to ensure that the macKey is NOT the same as the derived key.
*/
UpdateDigest(digest, "JPAKE_KC");
return DigestUtilities.DoFinal(digest);
}
/// <summary>
/// Validates the MacTag received from the partner participant.
///
/// throws CryptoException if the participantId strings are equal.
/// </summary>
public static void ValidateMacTag(string participantId, string partnerParticipantId,
BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4,
BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag)
{
/*
* Calculate the expected MacTag using the parameters as the partner
* would have used when the partner called calculateMacTag.
*
* i.e. basically all the parameters are reversed.
* participantId <-> partnerParticipantId
* x1 <-> x3
* x2 <-> x4
*/
BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest);
if (!expectedMacTag.Equals(partnerMacTag))
{
throw new CryptoException(
"Partner MacTag validation failed. "
+ "Therefore, the password, MAC, or digest algorithm of each participant does not match.");
}
}
private static void UpdateDigest(IDigest digest, BigInteger bigInteger)
{
UpdateDigest(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
}
private static void UpdateDigest(IDigest digest, string str)
{
UpdateDigest(digest, Encoding.UTF8.GetBytes(str));
}
private static void UpdateDigest(IDigest digest, byte[] bytes)
{
digest.BlockUpdate(bytes, 0, bytes.Length);
Arrays.Fill(bytes, (byte)0);
}
private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger)
{
UpdateDigestIncludingSize(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
}
private static void UpdateDigestIncludingSize(IDigest digest, string str)
{
UpdateDigestIncludingSize(digest, Encoding.UTF8.GetBytes(str));
}
private static void UpdateDigestIncludingSize(IDigest digest, byte[] bytes)
{
digest.BlockUpdate(IntToByteArray(bytes.Length), 0, 4);
digest.BlockUpdate(bytes, 0, bytes.Length);
Arrays.Fill(bytes, (byte)0);
}
private static void UpdateMac(IMac mac, BigInteger bigInteger)
{
UpdateMac(mac, BigIntegers.AsUnsignedByteArray(bigInteger));
}
private static void UpdateMac(IMac mac, string str)
{
UpdateMac(mac, Encoding.UTF8.GetBytes(str));
}
private static void UpdateMac(IMac mac, byte[] bytes)
{
mac.BlockUpdate(bytes, 0, bytes.Length);
Arrays.Fill(bytes, (byte)0);
}
private static byte[] IntToByteArray(int value)
{
return Pack.UInt32_To_BE((uint)value);
}
}
}
#pragma warning restore
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 044dab773d7b78b4a890e0ed2e9f9db8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: