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);
}
}
}