From 8682ed8e39998e340ce43c5955da1e1689857124 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 2 Oct 2022 01:49:41 +0700 Subject: Add support for C1C3C2 mode to SM2Engine - Refactoring, including span-based variants --- crypto/src/crypto/engines/SM2Engine.cs | 233 ++++++++++++++++++++++++--- crypto/test/src/crypto/test/SM2EngineTest.cs | 91 ++++++++++- 2 files changed, 302 insertions(+), 22 deletions(-) diff --git a/crypto/src/crypto/engines/SM2Engine.cs b/crypto/src/crypto/engines/SM2Engine.cs index 1a121c192..36593cddc 100644 --- a/crypto/src/crypto/engines/SM2Engine.cs +++ b/crypto/src/crypto/engines/SM2Engine.cs @@ -16,7 +16,13 @@ namespace Org.BouncyCastle.Crypto.Engines /// public class SM2Engine { + public enum Mode + { + C1C2C3, C1C3C2 + } + private readonly IDigest mDigest; + private readonly Mode mMode; private bool mForEncryption; private ECKeyParameters mECKey; @@ -29,9 +35,20 @@ namespace Org.BouncyCastle.Crypto.Engines { } + public SM2Engine(Mode mode) + : this(new SM3Digest(), mode) + { + } + public SM2Engine(IDigest digest) + : this(digest, Mode.C1C2C3) + { + } + + public SM2Engine(IDigest digest, Mode mode) { - this.mDigest = digest; + mDigest = digest; + mMode = mode; } public virtual void Init(bool forEncryption, ICipherParameters param) @@ -63,10 +80,11 @@ namespace Org.BouncyCastle.Crypto.Engines public virtual byte[] ProcessBlock(byte[] input, int inOff, int inLen) { if ((inOff + inLen) > input.Length || inLen == 0) - { throw new DataLengthException("input buffer too short"); - } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return ProcessBlock(input.AsSpan(inOff, inLen)); +#else if (mForEncryption) { return Encrypt(input, inOff, inLen); @@ -75,13 +93,141 @@ namespace Org.BouncyCastle.Crypto.Engines { return Decrypt(input, inOff, inLen); } +#endif } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public virtual byte[] ProcessBlock(ReadOnlySpan input) + { + if (input.Length == 0) + throw new DataLengthException("input buffer too short"); + + if (mForEncryption) + { + return Encrypt(input); + } + else + { + return Decrypt(input); + } + } +#endif + protected virtual ECMultiplier CreateBasePointMultiplier() { return new FixedPointCombMultiplier(); } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private byte[] Encrypt(ReadOnlySpan input) + { + byte[] c2 = input.ToArray(); + + ECMultiplier multiplier = CreateBasePointMultiplier(); + + BigInteger k; + ECPoint kPB; + do + { + k = NextK(); + kPB = ((ECPublicKeyParameters)mECKey).Q.Multiply(k).Normalize(); + + Kdf(mDigest, kPB, c2); + } + while (NotEncrypted(c2, input)); + + ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize(); + + Span c1 = stackalloc byte[c1P.GetEncodedLength(false)]; + c1P.EncodeTo(false, c1); + + AddFieldElement(mDigest, kPB.AffineXCoord); + mDigest.BlockUpdate(input); + AddFieldElement(mDigest, kPB.AffineYCoord); + + Span c3 = stackalloc byte[mDigest.GetDigestSize()]; + mDigest.DoFinal(c3); + + switch (mMode) + { + case Mode.C1C3C2: + return Arrays.Concatenate(c1, c3, c2); + default: + return Arrays.Concatenate(c1, c2, c3); + } + } + + private byte[] Decrypt(ReadOnlySpan input) + { + int c1Length = mCurveLength * 2 + 1; + ECPoint c1P = mECParams.Curve.DecodePoint(input[..c1Length]); + + ECPoint s = c1P.Multiply(mECParams.H); + if (s.IsInfinity) + throw new InvalidCipherTextException("[h]C1 at infinity"); + + c1P = c1P.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize(); + + int digestSize = mDigest.GetDigestSize(); + int c2Length = input.Length - c1Length - digestSize; + byte[] c2 = new byte[c2Length]; + + if (mMode == Mode.C1C3C2) + { + input[(c1Length + digestSize)..].CopyTo(c2); + } + else + { + input[c1Length..(c1Length + c2Length)].CopyTo(c2); + } + + Kdf(mDigest, c1P, c2); + + AddFieldElement(mDigest, c1P.AffineXCoord); + mDigest.BlockUpdate(c2); + AddFieldElement(mDigest, c1P.AffineYCoord); + + Span c3 = stackalloc byte[mDigest.GetDigestSize()]; + mDigest.DoFinal(c3); + + int check = 0; + if (mMode == Mode.C1C3C2) + { + for (int i = 0; i != c3.Length; i++) + { + check |= c3[i] ^ input[c1Length + i]; + } + } + else + { + for (int i = 0; i != c3.Length; i++) + { + check |= c3[i] ^ input[c1Length + c2.Length + i]; + } + } + + c3.Fill(0); + + if (check != 0) + { + Arrays.Fill(c2, 0); + throw new InvalidCipherTextException("invalid cipher text"); + } + + return c2; + } + + private bool NotEncrypted(ReadOnlySpan encData, ReadOnlySpan input) + { + for (int i = 0; i != encData.Length; i++) + { + if (encData[i] != input[i]) + return false; + } + + return true; + } +#else private byte[] Encrypt(byte[] input, int inOff, int inLen) { byte[] c2 = new byte[inLen]; @@ -90,29 +236,34 @@ namespace Org.BouncyCastle.Crypto.Engines ECMultiplier multiplier = CreateBasePointMultiplier(); - byte[] c1; + BigInteger k; ECPoint kPB; do { - BigInteger k = NextK(); - - ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize(); - - c1 = c1P.GetEncoded(false); - + k = NextK(); kPB = ((ECPublicKeyParameters)mECKey).Q.Multiply(k).Normalize(); Kdf(mDigest, kPB, c2); } while (NotEncrypted(c2, input, inOff)); + ECPoint c1P = multiplier.Multiply(mECParams.G, k).Normalize(); + + byte[] c1 = c1P.GetEncoded(false); + AddFieldElement(mDigest, kPB.AffineXCoord); mDigest.BlockUpdate(input, inOff, inLen); AddFieldElement(mDigest, kPB.AffineYCoord); byte[] c3 = DigestUtilities.DoFinal(mDigest); - return Arrays.ConcatenateAll(c1, c2, c3); + switch (mMode) + { + case Mode.C1C3C2: + return Arrays.ConcatenateAll(c1, c3, c2); + default: + return Arrays.ConcatenateAll(c1, c2, c3); + } } private byte[] Decrypt(byte[] input, int inOff, int inLen) @@ -129,9 +280,17 @@ namespace Org.BouncyCastle.Crypto.Engines c1P = c1P.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize(); - byte[] c2 = new byte[inLen - c1.Length - mDigest.GetDigestSize()]; + int digestSize = mDigest.GetDigestSize(); + byte[] c2 = new byte[inLen - c1.Length - digestSize]; - Array.Copy(input, inOff + c1.Length, c2, 0, c2.Length); + if (mMode == Mode.C1C3C2) + { + Array.Copy(input, inOff + c1.Length + digestSize, c2, 0, c2.Length); + } + else + { + Array.Copy(input, inOff + c1.Length, c2, 0, c2.Length); + } Kdf(mDigest, c1P, c2); @@ -142,9 +301,19 @@ namespace Org.BouncyCastle.Crypto.Engines byte[] c3 = DigestUtilities.DoFinal(mDigest); int check = 0; - for (int i = 0; i != c3.Length; i++) + if (mMode == Mode.C1C3C2) + { + for (int i = 0; i != c3.Length; i++) + { + check |= c3[i] ^ input[inOff + c1.Length + i]; + } + } + else { - check |= c3[i] ^ input[inOff + c1.Length + c2.Length + i]; + for (int i = 0; i != c3.Length; i++) + { + check |= c3[i] ^ input[inOff + c1.Length + c2.Length + i]; + } } Arrays.Fill(c1, 0); @@ -164,18 +333,21 @@ namespace Org.BouncyCastle.Crypto.Engines for (int i = 0; i != encData.Length; i++) { if (encData[i] != input[inOff + i]) - { return false; - } } return true; } +#endif private void Kdf(IDigest digest, ECPoint c1, byte[] encData) { int digestSize = digest.GetDigestSize(); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Span buf = stackalloc byte[System.Math.Max(4, digestSize)]; +#else byte[] buf = new byte[System.Math.Max(4, digestSize)]; +#endif int off = 0; IMemoable memo = digest as IMemoable; @@ -202,16 +374,32 @@ namespace Org.BouncyCastle.Crypto.Engines AddFieldElement(digest, c1.AffineYCoord); } + int xorLen = System.Math.Min(digestSize, encData.Length - off); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Pack.UInt32_To_BE(++ct, buf); + digest.BlockUpdate(buf[..4]); + digest.DoFinal(buf); + Xor(encData.AsSpan(off, xorLen), buf); +#else 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); +#endif off += xorLen; } } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Xor(Span data, ReadOnlySpan kdfOut) + { + for (int i = 0; i != data.Length; i++) + { + data[i] ^= kdfOut[i]; + } + } +#else private void Xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { for (int i = 0; i != dRemaining; i++) @@ -219,6 +407,7 @@ namespace Org.BouncyCastle.Crypto.Engines data[dOff + i] ^= kdfOut[i]; } } +#endif private BigInteger NextK() { @@ -236,8 +425,14 @@ namespace Org.BouncyCastle.Crypto.Engines private void AddFieldElement(IDigest digest, ECFieldElement v) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Span p = stackalloc byte[v.GetEncodedLength()]; + v.EncodeTo(p); + digest.BlockUpdate(p); +#else byte[] p = v.GetEncoded(); digest.BlockUpdate(p, 0, p.Length); +#endif } } } diff --git a/crypto/test/src/crypto/test/SM2EngineTest.cs b/crypto/test/src/crypto/test/SM2EngineTest.cs index 85fc0dfc3..cc01679c0 100644 --- a/crypto/test/src/crypto/test/SM2EngineTest.cs +++ b/crypto/test/src/crypto/test/SM2EngineTest.cs @@ -39,7 +39,8 @@ namespace Org.BouncyCastle.Crypto.Tests ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); - ECKeyGenerationParameters aKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0", 16)); + ECKeyGenerationParameters aKeyGenParams = new ECKeyGenerationParameters(domainParams, + new TestRandomBigInteger("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0", 16)); keyPairGenerator.Init(aKeyGenParams); @@ -52,7 +53,8 @@ namespace Org.BouncyCastle.Crypto.Tests byte[] m = Strings.ToByteArray("encryption standard"); - sm2Engine.Init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16))); + sm2Engine.Init(true, new ParametersWithRandom(aPub, + new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16))); byte[] enc = sm2Engine.ProcessBlock(m, 0, m.Length); @@ -89,7 +91,89 @@ namespace Org.BouncyCastle.Crypto.Tests m[i] = (byte)i; } - sm2Engine.Init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16))); + 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 DoEngineTestFpC1C3C2() + { + 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_H = BigInteger.One; + 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, SM2_ECC_N, SM2_ECC_H); + + 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(SM2Engine.Mode.C1C3C2); + + 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" + + "B8 9C3D7360 C30156FA B7C80A02" + + "76712DA9 D8094A63 4B766D3A 285E0748 0653426D 650053 A89B41C4 18B0C3AA D00D886C 00286467"), 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(SM2Engine.Mode.C1C3C2); + + 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); @@ -150,6 +234,7 @@ namespace Org.BouncyCastle.Crypto.Tests { DoEngineTestFp(); DoEngineTestF2m(); + DoEngineTestFpC1C3C2(); } [Test] -- cgit 1.4.1