<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />

SM2Engine

public class SM2Engine
SM2 public key encryption engine - based on https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02.
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; using System; 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; private ECDomainParameters mECParams; private int mCurveLength; private SecureRandom mRandom; public SM2Engine() : this(new SM3Digest()) { } public SM2Engine(Mode mode) : this(new SM3Digest(), mode) { } public SM2Engine(IDigest digest) : this(digest, Mode.C1C2C3) { } public SM2Engine(IDigest digest, Mode mode) { mDigest = digest; mMode = mode; } public virtual void Init(bool forEncryption, ICipherParameters param) { mForEncryption = forEncryption; SecureRandom secureRandom = null; ParametersWithRandom parametersWithRandom = param as ParametersWithRandom; if (parametersWithRandom != null) { param = parametersWithRandom.Parameters; secureRandom = parametersWithRandom.Random; } mECKey = (ECKeyParameters)param; mECParams = mECKey.Parameters; if (forEncryption) { mRandom = CryptoServicesRegistrar.GetSecureRandom(secureRandom); if (((ECPublicKeyParameters)mECKey).Q.Multiply(mECParams.H).IsInfinity) throw new ArgumentException("invalid key: [h]Q at infinity"); } else mRandom = null; mCurveLength = mECParams.Curve.FieldElementEncodingLength; } public virtual byte[] ProcessBlock(byte[] input, int inOff, int inLen) { if (inOff + inLen > input.Length || inLen == 0) throw new DataLengthException("input buffer too short"); return ProcessBlock(input.AsSpan(inOff, inLen)); } public virtual byte[] ProcessBlock(ReadOnlySpan<byte> input) { if (input.Length == 0) throw new DataLengthException("input buffer too short"); if (mForEncryption) return Encrypt(input); return Decrypt(input); } protected virtual ECMultiplier CreateBasePointMultiplier() { return new FixedPointCombMultiplier(); } private unsafe byte[] Encrypt(ReadOnlySpan<byte> input) { byte[] array = input.ToArray(); ECMultiplier eCMultiplier = CreateBasePointMultiplier(); BigInteger bigInteger; ECPoint eCPoint; do { bigInteger = NextK(); eCPoint = ((ECPublicKeyParameters)mECKey).Q.Multiply(bigInteger).Normalize(); Kdf(mDigest, eCPoint, array); } while (NotEncrypted(array, input)); ECPoint eCPoint2 = eCMultiplier.Multiply(mECParams.G, bigInteger).Normalize(); int encodedLength = eCPoint2.GetEncodedLength(false); Span<byte> span; if (encodedLength <= 512) { int num = encodedLength; span = new Span<byte>(stackalloc byte[(int)(uint)num], num); } else span = new byte[encodedLength]; Span<byte> span2 = span; eCPoint2.EncodeTo(false, span2); AddFieldElement(mDigest, eCPoint.AffineXCoord); mDigest.BlockUpdate(input); AddFieldElement(mDigest, eCPoint.AffineYCoord); int digestSize = mDigest.GetDigestSize(); if (digestSize <= 128) { int num = digestSize; span = new Span<byte>(stackalloc byte[(int)(uint)num], num); } else span = new byte[digestSize]; Span<byte> span3 = span; mDigest.DoFinal(span3); if (mMode == Mode.C1C3C2) return Arrays.Concatenate(span2, span3, array); return Arrays.Concatenate(span2, array, span3); } private unsafe byte[] Decrypt(ReadOnlySpan<byte> input) { int num = mCurveLength * 2 + 1; ECPoint eCPoint = mECParams.Curve.DecodePoint(input.Slice(0, num)); if (eCPoint.Multiply(mECParams.H).IsInfinity) throw new InvalidCipherTextException("[h]C1 at infinity"); eCPoint = eCPoint.Multiply(((ECPrivateKeyParameters)mECKey).D).Normalize(); int digestSize = mDigest.GetDigestSize(); int num2 = input.Length - num - digestSize; byte[] array = new byte[num2]; ReadOnlySpan<byte> readOnlySpan; if (mMode == Mode.C1C3C2) { int num3 = num + digestSize; readOnlySpan = input.Slice(num3, input.Length - num3); readOnlySpan.CopyTo(array); } else { int num3 = num; readOnlySpan = input.Slice(num3, num + num2 - num3); readOnlySpan.CopyTo(array); } Kdf(mDigest, eCPoint, array); AddFieldElement(mDigest, eCPoint.AffineXCoord); mDigest.BlockUpdate(array); AddFieldElement(mDigest, eCPoint.AffineYCoord); Span<byte> span; if (digestSize <= 128) { int num3 = digestSize; span = new Span<byte>(stackalloc byte[(int)(uint)num3], num3); } else span = new byte[digestSize]; Span<byte> output = span; mDigest.DoFinal(output); int num4 = 0; if (mMode == Mode.C1C3C2) { for (int i = 0; i != output.Length; i++) { num4 |= (output[i] ^ input[num + i]); } } else { for (int j = 0; j != output.Length; j++) { num4 |= (output[j] ^ input[num + array.Length + j]); } } output.Fill(0); if (num4 != 0) { Arrays.Fill(array, 0); throw new InvalidCipherTextException("invalid cipher text"); } return array; } 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; } private unsafe void Kdf(IDigest digest, ECPoint c1, byte[] encData) { int digestSize = digest.GetDigestSize(); int num = System.Math.Max(4, digestSize); Span<byte> span; if (num <= 128) { int num2 = num; span = new Span<byte>(stackalloc byte[(int)(uint)num2], num2); } else span = new byte[num]; Span<byte> span2 = span; int i = 0; IMemoable memoable = digest as IMemoable; IMemoable other = null; if (memoable != null) { AddFieldElement(digest, c1.AffineXCoord); AddFieldElement(digest, c1.AffineYCoord); other = memoable.Copy(); } uint num3 = 0; int num4; for (; i < encData.Length; i += num4) { if (memoable != null) memoable.Reset(other); else { AddFieldElement(digest, c1.AffineXCoord); AddFieldElement(digest, c1.AffineYCoord); } num4 = System.Math.Min(digestSize, encData.Length - i); Pack.UInt32_To_BE(++num3, span2); digest.BlockUpdate(span2.Slice(0, 4)); digest.DoFinal(span2); Bytes.XorTo(num4, span2, encData.AsSpan(i)); } } private BigInteger NextK() { int bitLength = mECParams.N.BitLength; BigInteger bigInteger; do { bigInteger = new BigInteger(bitLength, mRandom); } while (bigInteger.SignValue == 0 || bigInteger.CompareTo(mECParams.N) >= 0); return bigInteger; } private unsafe void AddFieldElement(IDigest digest, ECFieldElement v) { int encodedLength = v.GetEncodedLength(); Span<byte> span; if (encodedLength <= 128) { int num = encodedLength; span = new Span<byte>(stackalloc byte[(int)(uint)num], num); } else span = new byte[encodedLength]; Span<byte> span2 = span; v.EncodeTo(span2); digest.BlockUpdate(span2); } } }