summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2017-09-16 15:21:21 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2017-09-16 15:21:21 +0700
commit574dbc0875687377c607a92765463986caa3da7a (patch)
treed461735ecdfa038c67fd9ead17c0c4b1e284b1e4
parentSM2 signing cleanup (diff)
downloadBouncyCastle.NET-ed25519-574dbc0875687377c607a92765463986caa3da7a.tar.xz
Port of SM2KeyExchange from Java
-rw-r--r--crypto/BouncyCastle.Android.csproj3
-rw-r--r--crypto/BouncyCastle.csproj3
-rw-r--r--crypto/BouncyCastle.iOS.csproj3
-rw-r--r--crypto/crypto.csproj20
-rw-r--r--crypto/src/crypto/agreement/SM2KeyExchange.cs272
-rw-r--r--crypto/src/crypto/parameters/SM2KeyExchangePrivateParameters.cs64
-rw-r--r--crypto/src/crypto/parameters/SM2KeyExchangePublicParameters.cs40
-rw-r--r--crypto/test/UnitTests.csproj1
-rw-r--r--crypto/test/src/crypto/test/RegressionTest.cs1
-rw-r--r--crypto/test/src/crypto/test/SM2KeyExchangeTest.cs229
10 files changed, 636 insertions, 0 deletions
diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index 926e0fd94..3a716e52e 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -696,6 +696,7 @@
     <Compile Include="src\crypto\agreement\ECDHWithKdfBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvWithKdfBasicAgreement.cs" />
+    <Compile Include="src\crypto\agreement\SM2KeyExchange.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakeParticipant.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroup.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroups.cs" />
@@ -928,6 +929,8 @@
     <Compile Include="src\crypto\parameters\RsaKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\RsaPrivateCrtKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\SkeinParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePrivateParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePublicParameters.cs" />
     <Compile Include="src\crypto\parameters\Srp6GroupParameters.cs" />
     <Compile Include="src\crypto\parameters\TweakableBlockCipherParameters.cs" />
     <Compile Include="src\crypto\prng\CryptoApiRandomGenerator.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index 463ba2a1c..346092b76 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -690,6 +690,7 @@
     <Compile Include="src\crypto\agreement\ECDHWithKdfBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvWithKdfBasicAgreement.cs" />
+    <Compile Include="src\crypto\agreement\SM2KeyExchange.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakeParticipant.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroup.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroups.cs" />
@@ -922,6 +923,8 @@
     <Compile Include="src\crypto\parameters\RsaKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\RsaPrivateCrtKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\SkeinParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePrivateParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePublicParameters.cs" />
     <Compile Include="src\crypto\parameters\Srp6GroupParameters.cs" />
     <Compile Include="src\crypto\parameters\TweakableBlockCipherParameters.cs" />
     <Compile Include="src\crypto\prng\CryptoApiRandomGenerator.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 783009d38..53597e3dd 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -691,6 +691,7 @@
     <Compile Include="src\crypto\agreement\ECDHWithKdfBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvBasicAgreement.cs" />
     <Compile Include="src\crypto\agreement\ECMqvWithKdfBasicAgreement.cs" />
+    <Compile Include="src\crypto\agreement\SM2KeyExchange.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakeParticipant.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroup.cs" />
     <Compile Include="src\crypto\agreement\jpake\JPakePrimeOrderGroups.cs" />
@@ -923,6 +924,8 @@
     <Compile Include="src\crypto\parameters\RsaKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\RsaPrivateCrtKeyParameters.cs" />
     <Compile Include="src\crypto\parameters\SkeinParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePrivateParameters.cs" />
+    <Compile Include="src\crypto\parameters\SM2KeyExchangePublicParameters.cs" />
     <Compile Include="src\crypto\parameters\Srp6GroupParameters.cs" />
     <Compile Include="src\crypto\parameters\TweakableBlockCipherParameters.cs" />
     <Compile Include="src\crypto\prng\CryptoApiRandomGenerator.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 509638ff1..f9d38edd5 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -3234,6 +3234,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\agreement\SM2KeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\agreement\jpake\JPakeParticipant.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4429,6 +4434,16 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\parameters\SM2KeyExchangePrivateParameters.cs" 
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\parameters\SM2KeyExchangePublicParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\parameters\Srp6GroupParameters.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -11920,6 +11935,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\test\SM2KeyExchangeTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\crypto\test\SM2SignerTest.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/agreement/SM2KeyExchange.cs b/crypto/src/crypto/agreement/SM2KeyExchange.cs
new file mode 100644
index 000000000..1cfcd6a3a
--- /dev/null
+++ b/crypto/src/crypto/agreement/SM2KeyExchange.cs
@@ -0,0 +1,272 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Agreement
+{
+    /// <summary>
+    /// SM2 Key Exchange protocol - based on https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02
+    /// </summary>
+    public class SM2KeyExchange
+    {
+        private readonly IDigest mDigest;
+
+        private byte[] mUserID;
+        private ECPrivateKeyParameters mStaticKey;
+        private ECPoint mStaticPubPoint;
+        private ECPoint mEphemeralPubPoint;
+        private ECDomainParameters mECParams;
+        private int mW;
+        private ECPrivateKeyParameters mEphemeralKey;
+        private bool mInitiator;
+
+        public SM2KeyExchange()
+            : this(new SM3Digest())
+        {
+        }
+
+        public SM2KeyExchange(IDigest digest)
+        {
+            this.mDigest = digest;
+        }
+
+        public virtual void Init(ICipherParameters privParam)
+        {
+            SM2KeyExchangePrivateParameters baseParam;
+
+            if (privParam is ParametersWithID)
+            {
+                baseParam = (SM2KeyExchangePrivateParameters)((ParametersWithID)privParam).Parameters;
+                mUserID = ((ParametersWithID)privParam).GetID();
+            }
+            else
+            {
+                baseParam = (SM2KeyExchangePrivateParameters)privParam;
+                mUserID = new byte[0];
+            }
+
+            mInitiator = baseParam.IsInitiator;
+            mStaticKey = baseParam.StaticPrivateKey;
+            mEphemeralKey = baseParam.EphemeralPrivateKey;
+            mECParams = mStaticKey.Parameters;
+            mStaticPubPoint = baseParam.StaticPublicPoint;
+            mEphemeralPubPoint = baseParam.EphemeralPublicPoint;
+            mW = mECParams.Curve.FieldSize / 2 - 1;
+        }
+
+        public virtual byte[] CalculateKey(int kLen, ICipherParameters pubParam)
+        {
+            SM2KeyExchangePublicParameters otherPub;
+            byte[] otherUserID;
+
+            if (pubParam is ParametersWithID)
+            {
+                otherPub = (SM2KeyExchangePublicParameters)((ParametersWithID)pubParam).Parameters;
+                otherUserID = ((ParametersWithID)pubParam).GetID();
+            }
+            else
+            {
+                otherPub = (SM2KeyExchangePublicParameters)pubParam;
+                otherUserID = new byte[0];
+            }
+
+            byte[] za = GetZ(mDigest, mUserID, mStaticPubPoint);
+            byte[] zb = GetZ(mDigest, otherUserID, otherPub.StaticPublicKey.Q);
+
+            ECPoint U = CalculateU(otherPub);
+
+            byte[] rv;
+            if (mInitiator)
+            {
+                rv = Kdf(U, za, zb, kLen);
+            }
+            else
+            {
+                rv = Kdf(U, zb, za, kLen);
+            }
+
+            return rv;
+        }
+
+        public virtual byte[][] CalculateKeyWithConfirmation(int kLen, byte[] confirmationTag, ICipherParameters pubParam)
+        {
+            SM2KeyExchangePublicParameters otherPub;
+            byte[] otherUserID;
+
+            if (pubParam is ParametersWithID)
+            {
+                otherPub = (SM2KeyExchangePublicParameters)((ParametersWithID)pubParam).Parameters;
+                otherUserID = ((ParametersWithID)pubParam).GetID();
+            }
+            else
+            {
+                otherPub = (SM2KeyExchangePublicParameters)pubParam;
+                otherUserID = new byte[0];
+            }
+
+            if (mInitiator && confirmationTag == null)
+                throw new ArgumentException("if initiating, confirmationTag must be set");
+
+            byte[] za = GetZ(mDigest, mUserID, mStaticPubPoint);
+            byte[] zb = GetZ(mDigest, otherUserID, otherPub.StaticPublicKey.Q);
+
+            ECPoint U = CalculateU(otherPub);
+
+            byte[] rv;
+            if (mInitiator)
+            {
+                rv = Kdf(U, za, zb, kLen);
+
+                byte[] inner = CalculateInnerHash(mDigest, U, za, zb, mEphemeralPubPoint, otherPub.EphemeralPublicKey.Q);
+
+                byte[] s1 = S1(mDigest, U, inner);
+
+                if (!Arrays.ConstantTimeAreEqual(s1, confirmationTag))
+                    throw new InvalidOperationException("confirmation tag mismatch");
+
+                return new byte[][] { rv, S2(mDigest, U, inner)};
+            }
+            else
+            {
+                rv = Kdf(U, zb, za, kLen);
+
+                byte[] inner = CalculateInnerHash(mDigest, U, zb, za, otherPub.EphemeralPublicKey.Q, mEphemeralPubPoint);
+
+                return new byte[][] { rv, S1(mDigest, U, inner), S2(mDigest, U, inner) };
+            }
+        }
+
+        protected virtual ECPoint CalculateU(SM2KeyExchangePublicParameters otherPub)
+        {
+            ECPoint p1 = otherPub.StaticPublicKey.Q;
+            ECPoint p2 = otherPub.EphemeralPublicKey.Q;
+
+            BigInteger x1 = Reduce(mEphemeralPubPoint.AffineXCoord.ToBigInteger());
+            BigInteger x2 = Reduce(p2.AffineXCoord.ToBigInteger());
+            BigInteger tA = mStaticKey.D.Add(x1.Multiply(mEphemeralKey.D));
+            BigInteger k1 = mECParams.H.Multiply(tA).Mod(mECParams.N);
+            BigInteger k2 = k1.Multiply(x2).Mod(mECParams.N);
+
+            return ECAlgorithms.SumOfTwoMultiplies(p1, k1, p2, k2).Normalize();
+        }
+
+        protected virtual byte[] Kdf(ECPoint u, byte[] za, byte[] zb, int klen)
+        {
+            int digestSize = mDigest.GetDigestSize();
+            byte[] buf = new byte[System.Math.Max(4, digestSize)];
+            byte[] rv = new byte[(klen + 7) / 8];
+            int off = 0;
+
+            IMemoable memo = mDigest as IMemoable;
+            IMemoable copy = null;
+
+            if (memo != null)
+            {
+                AddFieldElement(mDigest, u.AffineXCoord);
+                AddFieldElement(mDigest, u.AffineYCoord);
+                mDigest.BlockUpdate(za, 0, za.Length);
+                mDigest.BlockUpdate(zb, 0, zb.Length);
+                copy = memo.Copy();
+            }
+
+            uint ct = 0;
+
+            while (off < rv.Length)
+            {
+                if (memo != null)
+                {
+                    memo.Reset(copy);
+                }
+                else
+                {
+                    AddFieldElement(mDigest, u.AffineXCoord);
+                    AddFieldElement(mDigest, u.AffineYCoord);
+                    mDigest.BlockUpdate(za, 0, za.Length);
+                    mDigest.BlockUpdate(zb, 0, zb.Length);
+                }
+
+                Pack.UInt32_To_BE(++ct, buf, 0);
+                mDigest.BlockUpdate(buf, 0, 4);
+                mDigest.DoFinal(buf, 0);
+
+                int copyLen = System.Math.Min(digestSize, rv.Length - off);
+                Array.Copy(buf, 0, rv, off, copyLen);
+                off += copyLen;
+            }
+
+            return rv;
+        }
+
+        //x1~=2^w+(x1 AND (2^w-1))
+        private BigInteger Reduce(BigInteger x)
+        {
+            return x.And(BigInteger.One.ShiftLeft(mW).Subtract(BigInteger.One)).SetBit(mW);
+        }
+
+        private byte[] S1(IDigest digest, ECPoint u, byte[] inner)
+        {
+            digest.Update((byte)0x02);
+            AddFieldElement(digest, u.AffineYCoord);
+            digest.BlockUpdate(inner, 0, inner.Length);
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        private byte[] CalculateInnerHash(IDigest digest, ECPoint u, byte[] za, byte[] zb, ECPoint p1, ECPoint p2)
+        {
+            AddFieldElement(digest, u.AffineXCoord);
+            digest.BlockUpdate(za, 0, za.Length);
+            digest.BlockUpdate(zb, 0, zb.Length);
+            AddFieldElement(digest, p1.AffineXCoord);
+            AddFieldElement(digest, p1.AffineYCoord);
+            AddFieldElement(digest, p2.AffineXCoord);
+            AddFieldElement(digest, p2.AffineYCoord);
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        private byte[] S2(IDigest digest, ECPoint u, byte[] inner)
+        {
+            digest.Update((byte)0x03);
+            AddFieldElement(digest, u.AffineYCoord);
+            digest.BlockUpdate(inner, 0, inner.Length);
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        private byte[] GetZ(IDigest digest, byte[] userID, ECPoint pubPoint)
+        {
+            AddUserID(digest, userID);
+
+            AddFieldElement(digest, mECParams.Curve.A);
+            AddFieldElement(digest, mECParams.Curve.B);
+            AddFieldElement(digest, mECParams.G.AffineXCoord);
+            AddFieldElement(digest, mECParams.G.AffineYCoord);
+            AddFieldElement(digest, pubPoint.AffineXCoord);
+            AddFieldElement(digest, pubPoint.AffineYCoord);
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        private void AddUserID(IDigest digest, byte[] userID)
+        {
+            uint len = (uint)(userID.Length * 8);
+
+            digest.Update((byte)(len >> 8));
+            digest.Update((byte)len);
+            digest.BlockUpdate(userID, 0, userID.Length);
+        }
+
+        private void AddFieldElement(IDigest digest, ECFieldElement v)
+        {
+            byte[] p = v.GetEncoded();
+            digest.BlockUpdate(p, 0, p.Length);
+        }
+    }
+}
diff --git a/crypto/src/crypto/parameters/SM2KeyExchangePrivateParameters.cs b/crypto/src/crypto/parameters/SM2KeyExchangePrivateParameters.cs
new file mode 100644
index 000000000..8afb61544
--- /dev/null
+++ b/crypto/src/crypto/parameters/SM2KeyExchangePrivateParameters.cs
@@ -0,0 +1,64 @@
+using System;
+
+using Org.BouncyCastle.Math.EC;
+
+namespace Org.BouncyCastle.Crypto.Parameters
+{
+    /// <summary>Private parameters for an SM2 key exchange.</summary>
+    /// <remarks>The ephemeralPrivateKey is used to calculate the random point used in the algorithm.</remarks>
+    public class SM2KeyExchangePrivateParameters
+        : ICipherParameters
+    {
+        private readonly bool mInitiator;
+        private readonly ECPrivateKeyParameters mStaticPrivateKey;
+        private readonly ECPoint mStaticPublicPoint;
+        private readonly ECPrivateKeyParameters mEphemeralPrivateKey;
+        private readonly ECPoint mEphemeralPublicPoint;
+
+        public SM2KeyExchangePrivateParameters(
+            bool initiator,
+            ECPrivateKeyParameters staticPrivateKey,
+            ECPrivateKeyParameters ephemeralPrivateKey)
+        {
+            if (staticPrivateKey == null)
+                throw new ArgumentNullException("staticPrivateKey");
+            if (ephemeralPrivateKey == null)
+                throw new ArgumentNullException("ephemeralPrivateKey");
+
+            ECDomainParameters parameters = staticPrivateKey.Parameters;
+            if (!parameters.Equals(ephemeralPrivateKey.Parameters))
+                throw new ArgumentException("Static and ephemeral private keys have different domain parameters");
+
+            this.mInitiator = initiator;
+            this.mStaticPrivateKey = staticPrivateKey;
+            this.mStaticPublicPoint = parameters.G.Multiply(staticPrivateKey.D).Normalize();
+            this.mEphemeralPrivateKey = ephemeralPrivateKey;
+            this.mEphemeralPublicPoint = parameters.G.Multiply(ephemeralPrivateKey.D).Normalize();
+        }
+
+        public virtual bool IsInitiator
+        {
+            get { return mInitiator; }
+        }
+
+        public virtual ECPrivateKeyParameters StaticPrivateKey
+        {
+            get { return mStaticPrivateKey; }
+        }
+
+        public virtual ECPoint StaticPublicPoint
+        {
+            get { return mStaticPublicPoint; }
+        }
+
+        public virtual ECPrivateKeyParameters EphemeralPrivateKey
+        {
+            get { return mEphemeralPrivateKey; }
+        }
+
+        public virtual ECPoint EphemeralPublicPoint
+        {
+            get { return mEphemeralPublicPoint; }
+        }
+    }
+}
diff --git a/crypto/src/crypto/parameters/SM2KeyExchangePublicParameters.cs b/crypto/src/crypto/parameters/SM2KeyExchangePublicParameters.cs
new file mode 100644
index 000000000..5c213159e
--- /dev/null
+++ b/crypto/src/crypto/parameters/SM2KeyExchangePublicParameters.cs
@@ -0,0 +1,40 @@
+using System;
+
+using Org.BouncyCastle.Math.EC;
+
+namespace Org.BouncyCastle.Crypto.Parameters
+{
+    /// <summary>Public parameters for an SM2 key exchange.</summary>
+    /// <remarks>In this case the ephemeralPublicKey provides the random point used in the algorithm.</remarks>
+    public class SM2KeyExchangePublicParameters
+        : ICipherParameters
+    {
+        private readonly ECPublicKeyParameters mStaticPublicKey;
+        private readonly ECPublicKeyParameters mEphemeralPublicKey;
+
+        public SM2KeyExchangePublicParameters(
+            ECPublicKeyParameters staticPublicKey,
+            ECPublicKeyParameters ephemeralPublicKey)
+        {
+            if (staticPublicKey == null)
+                throw new ArgumentNullException("staticPublicKey");
+            if (ephemeralPublicKey == null)
+                throw new ArgumentNullException("ephemeralPublicKey");
+            if (!staticPublicKey.Parameters.Equals(ephemeralPublicKey.Parameters))
+                throw new ArgumentException("Static and ephemeral public keys have different domain parameters");
+
+            this.mStaticPublicKey = staticPublicKey;
+            this.mEphemeralPublicKey = ephemeralPublicKey;
+        }
+
+        public virtual ECPublicKeyParameters StaticPublicKey
+        {
+            get { return mStaticPublicKey; }
+        }
+
+        public virtual ECPublicKeyParameters EphemeralPublicKey
+        {
+            get { return mEphemeralPublicKey; }
+        }
+    }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index f875c22b1..bd7ad5787 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -256,6 +256,7 @@
     <Compile Include="src\crypto\test\SHA512HMacTest.cs" />
     <Compile Include="src\crypto\test\SHA512t224DigestTest.cs" />
     <Compile Include="src\crypto\test\SHA512t256DigestTest.cs" />
+    <Compile Include="src\crypto\test\SM2KeyExchangeTest.cs" />
     <Compile Include="src\crypto\test\SM2SignerTest.cs" />
     <Compile Include="src\crypto\test\SM3DigestTest.cs" />
     <Compile Include="src\crypto\test\SkeinDigestTest.cs" />
diff --git a/crypto/test/src/crypto/test/RegressionTest.cs b/crypto/test/src/crypto/test/RegressionTest.cs
index f2a92fab3..bc857b5d0 100644
--- a/crypto/test/src/crypto/test/RegressionTest.cs
+++ b/crypto/test/src/crypto/test/RegressionTest.cs
@@ -128,6 +128,7 @@ namespace Org.BouncyCastle.Crypto.Tests
             new X931SignerTest(),
             new KeccakDigestTest(),
             new ShakeDigestTest(),
+            new SM2KeyExchangeTest(),
             new SM2SignerTest(),
         };
 
diff --git a/crypto/test/src/crypto/test/SM2KeyExchangeTest.cs b/crypto/test/src/crypto/test/SM2KeyExchangeTest.cs
new file mode 100644
index 000000000..d7a2650eb
--- /dev/null
+++ b/crypto/test/src/crypto/test/SM2KeyExchangeTest.cs
@@ -0,0 +1,229 @@
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+    public class SM2KeyExchangeTest
+        : SimpleTest
+    {
+        public override string Name
+        {
+            get { return "SM2KeyExchange"; }
+        }
+
+        private void DoKeyExchangeTestFp()
+        {
+            BigInteger SM2_ECC_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16);
+            BigInteger SM2_ECC_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16);
+            BigInteger SM2_ECC_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16);
+            BigInteger SM2_ECC_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16);
+            BigInteger SM2_ECC_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16);
+            BigInteger SM2_ECC_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16);
+
+            ECCurve curve = new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B);
+
+            ECPoint g = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);
+            ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);
+
+            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+
+            ECKeyGenerationParameters aKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("6FCBA2EF9AE0AB902BC3BDE3FF915D44BA4CC78F88E2F8E7F8996D3B8CCEEDEE", 16));
+
+            keyPairGenerator.Init(aKeyGenParams);
+
+            AsymmetricCipherKeyPair aKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aPub = (ECPublicKeyParameters)aKp.Public;
+            ECPrivateKeyParameters aPriv = (ECPrivateKeyParameters)aKp.Private;
+
+            ECKeyGenerationParameters aeKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563", 16));
+
+            keyPairGenerator.Init(aeKeyGenParams);
+
+            AsymmetricCipherKeyPair aeKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aePub = (ECPublicKeyParameters)aeKp.Public;
+            ECPrivateKeyParameters aePriv = (ECPrivateKeyParameters)aeKp.Private;
+
+            ECKeyGenerationParameters bKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("5E35D7D3F3C54DBAC72E61819E730B019A84208CA3A35E4C2E353DFCCB2A3B53", 16));
+
+            keyPairGenerator.Init(bKeyGenParams);
+
+            AsymmetricCipherKeyPair bKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters bPub = (ECPublicKeyParameters)bKp.Public;
+            ECPrivateKeyParameters bPriv = (ECPrivateKeyParameters)bKp.Private;
+
+            ECKeyGenerationParameters beKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80", 16));
+
+            keyPairGenerator.Init(beKeyGenParams);
+
+            AsymmetricCipherKeyPair beKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters bePub = (ECPublicKeyParameters)beKp.Public;
+            ECPrivateKeyParameters bePriv = (ECPrivateKeyParameters)beKp.Private;
+
+            SM2KeyExchange exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(true, aPriv, aePriv), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            byte[] k1 = exch.CalculateKey(128, new ParametersWithID(new SM2KeyExchangePublicParameters(bPub, bePub), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            IsTrue("key 1 wrong", Arrays.AreEqual(Hex.Decode("55b0ac62a6b927ba23703832c853ded4"), k1));
+
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(false, bPriv, bePriv), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            byte[] k2 = exch.CalculateKey(128, new ParametersWithID(new SM2KeyExchangePublicParameters(aPub, aePub), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            IsTrue("key 2 wrong", Arrays.AreEqual(Hex.Decode("55b0ac62a6b927ba23703832c853ded4"), k2));
+
+            // with key confirmation
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(false, bPriv, bePriv), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            byte[][] vals2 = exch.CalculateKeyWithConfirmation(128, null, new ParametersWithID(new SM2KeyExchangePublicParameters(aPub, aePub), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            IsTrue("key 2 wrong", Arrays.AreEqual(Hex.Decode("55b0ac62a6b927ba23703832c853ded4"), k2));
+      
+            IsTrue("conf a tag 2 wrong", Arrays.AreEqual(Hex.Decode("284C8F198F141B502E81250F1581C7E9EEB4CA6990F9E02DF388B45471F5BC5C"), vals2[1]));
+            IsTrue("conf b tag 2 wrong", Arrays.AreEqual(Hex.Decode("23444DAF8ED7534366CB901C84B3BDBB63504F4065C1116C91A4C00697E6CF7A"), vals2[2]));
+
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(true, aPriv, aePriv), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            byte[][] vals1 = exch.CalculateKeyWithConfirmation(128, vals2[1], new ParametersWithID(new SM2KeyExchangePublicParameters(bPub, bePub), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            IsTrue("conf key 1 wrong", Arrays.AreEqual(Hex.Decode("55b0ac62a6b927ba23703832c853ded4"), vals1[0]));
+            IsTrue("conf tag 1 wrong", Arrays.AreEqual(Hex.Decode("23444DAF8ED7534366CB901C84B3BDBB63504F4065C1116C91A4C00697E6CF7A"), vals1[1]));
+        }
+
+        private void DoKeyExchangeTestF2m()
+        {
+            BigInteger SM2_ECC_A = new BigInteger("00", 16);
+            BigInteger SM2_ECC_B = new BigInteger("E78BCD09746C202378A7E72B12BCE00266B9627ECB0B5A25367AD1AD4CC6242B", 16);
+            BigInteger SM2_ECC_N = new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC972CF7E6B6F900945B3C6A0CF6161D", 16);
+            BigInteger SM2_ECC_GX = new BigInteger("00CDB9CA7F1E6B0441F658343F4B10297C0EF9B6491082400A62E7A7485735FADD", 16);
+            BigInteger SM2_ECC_GY = new BigInteger("013DE74DA65951C4D76DC89220D5F7777A611B1C38BAE260B175951DC8060C2B3E", 16);
+            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
+
+            ECCurve curve = new F2mCurve(257, 12, SM2_ECC_A, SM2_ECC_B);
+
+            ECPoint g = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);
+            ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N, SM2_ECC_H);
+        
+            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+
+            ECKeyGenerationParameters aKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("4813903D254F2C20A94BC5704238496954BB5279F861952EF2C5298E84D2CEAA", 16));
+
+            keyPairGenerator.Init(aKeyGenParams);
+
+            AsymmetricCipherKeyPair aKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aPub = (ECPublicKeyParameters)aKp.Public;
+            ECPrivateKeyParameters aPriv = (ECPrivateKeyParameters)aKp.Private;
+
+            ECKeyGenerationParameters aeKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("54A3D6673FF3A6BD6B02EBB164C2A3AF6D4A4906229D9BFCE68CC366A2E64BA4", 16));
+
+            keyPairGenerator.Init(aeKeyGenParams);
+
+            AsymmetricCipherKeyPair aeKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aePub = (ECPublicKeyParameters)aeKp.Public;
+            ECPrivateKeyParameters aePriv = (ECPrivateKeyParameters)aeKp.Private;
+
+            ECKeyGenerationParameters bKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("08F41BAE0922F47C212803FE681AD52B9BF28A35E1CD0EC273A2CF813E8FD1DC", 16));
+
+            keyPairGenerator.Init(bKeyGenParams);
+
+            AsymmetricCipherKeyPair bKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters bPub = (ECPublicKeyParameters)bKp.Public;
+            ECPrivateKeyParameters bPriv = (ECPrivateKeyParameters)bKp.Private;
+
+            ECKeyGenerationParameters beKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("1F21933387BEF781D0A8F7FD708C5AE0A56EE3F423DBC2FE5BDF6F068C53F7AD", 16));
+
+            keyPairGenerator.Init(beKeyGenParams);
+
+            AsymmetricCipherKeyPair beKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters bePub = (ECPublicKeyParameters)beKp.Public;
+            ECPrivateKeyParameters bePriv = (ECPrivateKeyParameters)beKp.Private;
+
+            SM2KeyExchange exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(true, aPriv, aePriv), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            byte[] k1 = exch.CalculateKey(128, new ParametersWithID(new SM2KeyExchangePublicParameters(bPub, bePub), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            // there appears to be typo for ZA in the draft
+            //IsTrue("F2m key 1 wrong", Arrays.AreEqual(Hex.Decode("4E587E5C66634F22D973A7D98BF8BE23"), k1));
+            IsTrue("F2m key 1 wrong", Arrays.AreEqual(Hex.Decode("8c2b03289aa7126555dc660cfc29fd74"), k1));
+
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(false, bPriv, bePriv), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            byte[] k2 = exch.CalculateKey(128, new ParametersWithID(new SM2KeyExchangePublicParameters(aPub, aePub), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            //IsTrue("F2m key 2 wrong", Arrays.AreEqual(Hex.Decode("4E587E5C66634F22D973A7D98BF8BE23"), k2));
+            IsTrue("F2m key 2 wrong", Arrays.AreEqual(Hex.Decode("8c2b03289aa7126555dc660cfc29fd74"), k2));
+
+            // with key confirmation
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(false, bPriv, bePriv), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            byte[][] vals2 = exch.CalculateKeyWithConfirmation(128, null, new ParametersWithID(new SM2KeyExchangePublicParameters(aPub, aePub), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            IsTrue("key 2 wrong", Arrays.AreEqual(Hex.Decode("8c2b03289aa7126555dc660cfc29fd74"), k2));
+
+            IsTrue("conf a tag 2 wrong", Arrays.AreEqual(Hex.Decode("d8294c4c0f0ac180feac95e8a0d786638c9e915b9a684b2348809af03a0de2a5"), vals2[1]));
+            IsTrue("conf b tag 2 wrong", Arrays.AreEqual(Hex.Decode("52089e706911b58fd5e7c7b2ab5cf32bb61e481ef1e114a1e33d99eec84b5a4f"), vals2[2]));
+
+            exch = new SM2KeyExchange();
+
+            exch.Init(new ParametersWithID(new SM2KeyExchangePrivateParameters(true, aPriv, aePriv), Strings.ToByteArray("ALICE123@YAHOO.COM")));
+
+            byte[][] vals1 = exch.CalculateKeyWithConfirmation(128, vals2[1], new ParametersWithID(new SM2KeyExchangePublicParameters(bPub, bePub), Strings.ToByteArray("BILL456@YAHOO.COM")));
+
+            IsTrue("conf key 1 wrong", Arrays.AreEqual(Hex.Decode("8c2b03289aa7126555dc660cfc29fd74"), vals1[0]));
+            IsTrue("conf tag 1 wrong", Arrays.AreEqual(Hex.Decode("52089e706911b58fd5e7c7b2ab5cf32bb61e481ef1e114a1e33d99eec84b5a4f"), vals1[1]));
+        }
+
+        public override void PerformTest()
+        {
+            DoKeyExchangeTestFp();
+            DoKeyExchangeTestF2m();
+        }
+
+        public static void Main(string[] args)
+        {
+            RunTest(new SM2KeyExchangeTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}