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
/// </summary>
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<byte> 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<byte> 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<byte> c1 = stackalloc byte[c1P.GetEncodedLength(false)];
+ c1P.EncodeTo(false, c1);
+
+ AddFieldElement(mDigest, kPB.AffineXCoord);
+ mDigest.BlockUpdate(input);
+ AddFieldElement(mDigest, kPB.AffineYCoord);
+
+ Span<byte> 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<byte> 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<byte> 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<byte> encData, ReadOnlySpan<byte> 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<byte> 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<byte> data, ReadOnlySpan<byte> 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<byte> 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]
|