summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2017-09-16 18:12:04 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2017-09-16 18:12:04 +0700
commit2b0b26630f78a64835e8f71b29c7f973a0fe05fd (patch)
tree73ebdbedf54d64e2818f753607e06e7948ce28f7
parentPort of SM2KeyExchange from Java (diff)
downloadBouncyCastle.NET-ed25519-2b0b26630f78a64835e8f71b29c7f973a0fe05fd.tar.xz
Port of SM2Engine from Java
-rw-r--r--crypto/BouncyCastle.Android.csproj1
-rw-r--r--crypto/BouncyCastle.csproj1
-rw-r--r--crypto/BouncyCastle.iOS.csproj1
-rw-r--r--crypto/crypto.csproj10
-rw-r--r--crypto/src/crypto/engines/SM2Engine.cs238
-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/SM2EngineTest.cs167
8 files changed, 420 insertions, 0 deletions
diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index 3a716e52e..1c7463c3b 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -791,6 +791,7 @@
     <Compile Include="src\crypto\engines\Salsa20Engine.cs" />
     <Compile Include="src\crypto\engines\SerpentEngine.cs" />
     <Compile Include="src\crypto\engines\SkipjackEngine.cs" />
+    <Compile Include="src\crypto\engines\SM2Engine.cs" />
     <Compile Include="src\crypto\engines\TEAEngine.cs" />
     <Compile Include="src\crypto\engines\ThreefishEngine.cs" />
     <Compile Include="src\crypto\engines\TwofishEngine.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index 346092b76..d5840b942 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -785,6 +785,7 @@
     <Compile Include="src\crypto\engines\Salsa20Engine.cs" />
     <Compile Include="src\crypto\engines\SerpentEngine.cs" />
     <Compile Include="src\crypto\engines\SkipjackEngine.cs" />
+    <Compile Include="src\crypto\engines\SM2Engine.cs" />
     <Compile Include="src\crypto\engines\TEAEngine.cs" />
     <Compile Include="src\crypto\engines\ThreefishEngine.cs" />
     <Compile Include="src\crypto\engines\TwofishEngine.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 53597e3dd..5e2ced2ad 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -786,6 +786,7 @@
     <Compile Include="src\crypto\engines\Salsa20Engine.cs" />
     <Compile Include="src\crypto\engines\SerpentEngine.cs" />
     <Compile Include="src\crypto\engines\SkipjackEngine.cs" />
+    <Compile Include="src\crypto\engines\SM2Engine.cs" />
     <Compile Include="src\crypto\engines\TEAEngine.cs" />
     <Compile Include="src\crypto\engines\ThreefishEngine.cs" />
     <Compile Include="src\crypto\engines\TwofishEngine.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index f9d38edd5..f872911e1 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -3719,6 +3719,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\engines\SM2Engine.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\engines\TEAEngine.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -11935,6 +11940,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\test\SM2EngineTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\crypto\test\SM2KeyExchangeTest.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/engines/SM2Engine.cs b/crypto/src/crypto/engines/SM2Engine.cs
new file mode 100644
index 000000000..e5f12bbbb
--- /dev/null
+++ b/crypto/src/crypto/engines/SM2Engine.cs
@@ -0,0 +1,238 @@
+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.Math.EC.Multiplier;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Engines
+{
+    /// <summary>
+    /// SM2 public key encryption engine - based on https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02.
+    /// </summary>
+    public class SM2Engine
+    {
+        private readonly IDigest mDigest;
+
+        private bool mForEncryption;
+        private ECKeyParameters mECKey;
+        private ECDomainParameters mECParams;
+        private int mCurveLength;
+        private SecureRandom mRandom;
+
+        public SM2Engine()
+            : this(new SM3Digest())
+        {
+        }
+
+        public SM2Engine(IDigest digest)
+        {
+            this.mDigest = digest;
+        }
+
+        public virtual void Init(bool forEncryption, ICipherParameters param)
+        {
+            this.mForEncryption = forEncryption;
+
+            if (forEncryption)
+            {
+                ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+                mECKey = (ECKeyParameters)rParam.Parameters;
+                mECParams = mECKey.Parameters;
+
+                ECPoint s = ((ECPublicKeyParameters)mECKey).Q.Multiply(mECParams.H);
+                if (s.IsInfinity)
+                    throw new ArgumentException("invalid key: [h]Q at infinity");
+
+                mRandom = rParam.Random;
+            }
+            else
+            {
+                mECKey = (ECKeyParameters)param;
+                mECParams = mECKey.Parameters;
+            }
+
+            mCurveLength = (mECParams.Curve.FieldSize + 7) / 8;
+        }
+
+        public virtual byte[] ProcessBlock(byte[] input, int inOff, int inLen)
+        {
+            if (mForEncryption)
+            {
+                return Encrypt(input, inOff, inLen);
+            }
+            else
+            {
+                return Decrypt(input, inOff, inLen);
+            }
+        }
+
+        protected virtual ECMultiplier CreateBasePointMultiplier()
+        {
+            return new FixedPointCombMultiplier();
+        }
+
+        private byte[] Encrypt(byte[] input, int inOff, int inLen)
+        {
+            byte[] c2 = new byte[inLen];
+
+            Array.Copy(input, inOff, c2, 0, c2.Length);
+
+            ECMultiplier multiplier = CreateBasePointMultiplier();
+
+            byte[] c1;
+            ECPoint kPB;
+            do
+            {
+                BigInteger k = NextK();
+
+                ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize();
+
+                c1 = c1P.GetEncoded(false);
+
+                kPB = ((ECPublicKeyParameters)mECKey).Q.Multiply(k).Normalize();
+
+                Kdf(mDigest, kPB, c2);
+            }
+            while (NotEncrypted(c2, input, inOff));
+
+            AddFieldElement(mDigest, kPB.AffineXCoord);
+            mDigest.BlockUpdate(input, inOff, inLen);
+            AddFieldElement(mDigest, kPB.AffineYCoord);
+
+            byte[] c3 = DigestUtilities.DoFinal(mDigest);
+
+            return Arrays.ConcatenateAll(c1, c2, c3);
+        }
+
+        private byte[] Decrypt(byte[] input, int inOff, int inLen)
+        {
+            byte[] c1 = new byte[mCurveLength * 2 + 1];
+
+            Array.Copy(input, inOff, c1, 0, c1.Length);
+
+            ECPoint c1P = mECParams.Curve.DecodePoint(c1);
+
+            ECPoint s = c1P.Multiply(mECParams.H);
+            if (s.IsInfinity)
+                throw new InvalidCipherTextException("[h]C1 at infinity");
+
+            c1P = c1P.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize();
+
+            byte[] c2 = new byte[inLen - c1.Length - mDigest.GetDigestSize()];
+
+            Array.Copy(input, inOff + c1.Length, c2, 0, c2.Length);
+
+            Kdf(mDigest, c1P, c2);
+
+            AddFieldElement(mDigest, c1P.AffineXCoord);
+            mDigest.BlockUpdate(c2, 0, c2.Length);
+            AddFieldElement(mDigest, c1P.AffineYCoord);
+
+            byte[] c3 = DigestUtilities.DoFinal(mDigest);
+
+            int check = 0;
+            for (int i = 0; i != c3.Length; i++)
+            {
+                check |= c3[i] ^ input[c1.Length + c2.Length + i];
+            }
+
+            Arrays.Fill(c1, 0);
+            Arrays.Fill(c3, 0);
+
+            if (check != 0)
+            {
+               Arrays.Fill(c2, 0);
+               throw new InvalidCipherTextException("invalid cipher text");
+            }
+
+            return c2;
+        }
+
+        private bool NotEncrypted(byte[] encData, byte[] input, int inOff)
+        {
+            for (int i = 0; i != encData.Length; i++)
+            {
+                if (encData[i] != input[inOff])
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private void Kdf(IDigest digest, ECPoint c1, byte[] encData)
+        {
+            int digestSize = digest.GetDigestSize();
+            byte[] buf = new byte[System.Math.Max(4, digestSize)];
+            int off = 0;
+
+            IMemoable memo = digest as IMemoable;
+            IMemoable copy = null;
+
+            if (memo != null)
+            {
+                AddFieldElement(digest, c1.AffineXCoord);
+                AddFieldElement(digest, c1.AffineYCoord);
+                copy = memo.Copy();
+            }
+
+            uint ct = 0;
+
+            while (off < encData.Length)
+            {
+                if (memo != null)
+                {
+                    memo.Reset(copy);
+                }
+                else
+                {
+                    AddFieldElement(digest, c1.AffineXCoord);
+                    AddFieldElement(digest, c1.AffineYCoord);
+                }
+
+                Pack.UInt32_To_BE(++ct, buf, 0);
+                digest.BlockUpdate(buf, 0, 4);
+                digest.DoFinal(buf, 0);
+
+                int xorLen = System.Math.Min(digestSize, encData.Length - off);
+                Xor(encData, buf, off, xorLen);
+                off += xorLen;
+            }
+        }
+
+        private void Xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining)
+        {
+            for (int i = 0; i != dRemaining; i++)
+            {
+                data[dOff + i] ^= kdfOut[i];
+            }
+        }
+
+        private BigInteger NextK()
+        {
+            int qBitLength = mECParams.N.BitLength;
+
+            BigInteger k;
+            do
+            {
+                k = new BigInteger(qBitLength, mRandom);
+            }
+            while (k.SignValue == 0 || k.CompareTo(mECParams.N) >= 0);
+
+            return k;
+        }
+
+        private void AddFieldElement(IDigest digest, ECFieldElement v)
+        {
+            byte[] p = v.GetEncoded();
+            digest.BlockUpdate(p, 0, p.Length);
+        }
+    }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index bd7ad5787..e9e0b3f4e 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\SM2EngineTest.cs" />
     <Compile Include="src\crypto\test\SM2KeyExchangeTest.cs" />
     <Compile Include="src\crypto\test\SM2SignerTest.cs" />
     <Compile Include="src\crypto\test\SM3DigestTest.cs" />
diff --git a/crypto/test/src/crypto/test/RegressionTest.cs b/crypto/test/src/crypto/test/RegressionTest.cs
index bc857b5d0..c065a1320 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 SM2EngineTest(),
             new SM2KeyExchangeTest(),
             new SM2SignerTest(),
         };
diff --git a/crypto/test/src/crypto/test/SM2EngineTest.cs b/crypto/test/src/crypto/test/SM2EngineTest.cs
new file mode 100644
index 000000000..8a1987d52
--- /dev/null
+++ b/crypto/test/src/crypto/test/SM2EngineTest.cs
@@ -0,0 +1,167 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Engines;
+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 SM2EngineTest
+        : SimpleTest
+    {
+        public override string Name
+        {
+            get { return "SM2Engine"; }
+        }
+
+        private void DoEngineTestFp()
+        {
+            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("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0", 16));
+
+            keyPairGenerator.Init(aKeyGenParams);
+
+            AsymmetricCipherKeyPair aKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aPub = (ECPublicKeyParameters)aKp.Public;
+            ECPrivateKeyParameters aPriv = (ECPrivateKeyParameters)aKp.Private;
+
+            SM2Engine sm2Engine = new SM2Engine();
+
+            byte[] m = Strings.ToByteArray("encryption standard");
+
+            sm2Engine.Init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16)));
+
+            byte[] enc = sm2Engine.ProcessBlock(m, 0, m.Length);
+
+            IsTrue("enc wrong", Arrays.AreEqual(Hex.Decode(
+                "04245C26 FB68B1DD DDB12C4B 6BF9F2B6 D5FE60A3 83B0D18D 1C4144AB F17F6252" +
+                "E776CB92 64C2A7E8 8E52B199 03FDC473 78F605E3 6811F5C0 7423A24B 84400F01" +
+                "B8650053 A89B41C4 18B0C3AA D00D886C 00286467 9C3D7360 C30156FA B7C80A02" +
+                "76712DA9 D8094A63 4B766D3A 285E0748 0653426D"), enc));
+
+            sm2Engine.Init(false, aPriv);
+
+            byte[] dec = sm2Engine.ProcessBlock(enc, 0, enc.Length);
+
+            IsTrue("dec wrong", Arrays.AreEqual(m, dec));
+
+            enc[80] = (byte)(enc[80] + 1);
+
+            try
+            {
+                sm2Engine.ProcessBlock(enc, 0, enc.Length);
+                Fail("no exception");
+            }
+            catch (InvalidCipherTextException e)
+            {
+                IsTrue("wrong exception", "invalid cipher text".Equals(e.Message));
+            }
+
+            // long message
+            sm2Engine = new SM2Engine();
+
+            m = new byte[4097];
+            for (int i = 0; i != m.Length; i++)
+            {
+                m[i] = (byte)i;
+            }
+
+            sm2Engine.Init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16)));
+
+            enc = sm2Engine.ProcessBlock(m, 0, m.Length);
+
+            sm2Engine.Init(false, aPriv);
+
+            dec = sm2Engine.ProcessBlock(enc, 0, enc.Length);
+
+            IsTrue("dec wrong", Arrays.AreEqual(m, dec));
+        }
+
+        private void DoEngineTestF2m()
+        {
+            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("56A270D17377AA9A367CFA82E46FA5267713A9B91101D0777B07FCE018C757EB", 16));
+
+            keyPairGenerator.Init(aKeyGenParams);
+
+            AsymmetricCipherKeyPair aKp = keyPairGenerator.GenerateKeyPair();
+
+            ECPublicKeyParameters aPub = (ECPublicKeyParameters)aKp.Public;
+            ECPrivateKeyParameters aPriv = (ECPrivateKeyParameters)aKp.Private;
+
+            SM2Engine sm2Engine = new SM2Engine();
+
+            byte[] m = Strings.ToByteArray("encryption standard");
+
+            sm2Engine.Init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("6D3B497153E3E92524E5C122682DBDC8705062E20B917A5F8FCDB8EE4C66663D", 16)));
+
+            byte[] enc = sm2Engine.ProcessBlock(m, 0, m.Length);
+
+            IsTrue("f2m enc wrong", Arrays.AreEqual(Hex.Decode(
+                "04019D23 6DDB3050 09AD52C5 1BB93270 9BD534D4 76FBB7B0 DF9542A8 A4D890A3" +
+                    "F2E100B2 3B938DC0 A94D1DF8 F42CF45D 2D6601BF 638C3D7D E75A29F0 2AFB7E45" +
+                    "E91771FD 55AC6213 C2A8A040 E4CAB5B2 6A9CFCDA 737373A4 8625D375 8FA37B3E" +
+                    "AB80E9CF CABA665E 3199EA15 A1FA8189 D96F5791 25E4"), enc));
+
+            sm2Engine.Init(false, aPriv);
+
+            byte[] dec = sm2Engine.ProcessBlock(enc, 0, enc.Length);
+
+            IsTrue("f2m dec wrong", Arrays.AreEqual(m, dec));
+        }
+
+        public override void PerformTest()
+        {
+            DoEngineTestFp();
+            DoEngineTestF2m();
+        }
+
+        public static void Main(string[] args)
+        {
+            RunTest(new SM2EngineTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}