From 0bf69a19a93e81a15f989ec2c4f31ba61e2da45c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 9 Nov 2022 23:00:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(tenpayv3):=20=E6=96=B0=E5=A2=9E=20SM3=20?= =?UTF-8?q?=E7=AE=97=E6=B3=95=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Utilities/SM3Utility.cs | 370 ++++++++++++++++++ .../TestCase_SM3UtilityTests.cs | 18 + 2 files changed, 388 insertions(+) create mode 100644 src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM3Utility.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM3UtilityTests.cs diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM3Utility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM3Utility.cs new file mode 100644 index 00000000..22730c36 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM3Utility.cs @@ -0,0 +1,370 @@ +using System; +using System.Text; +using Org.BouncyCastle.Crypto; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities +{ + /// + /// SM3 算法工具类。 + /// + public static class SM3Utility + { + internal static class BitOperator + { + public static int URShift(int number, int bits) + { + if (number >= 0) + return number >> bits; + else + return (number >> bits) + (2 << ~bits); + } + + public static int URShift(int number, long bits) + { + return URShift(number, (int)bits); + } + + public static long URShift(long number, int bits) + { + if (number >= 0) + return number >> bits; + else + return (number >> bits) + (2L << ~bits); + } + + public static long URShift(long number, long bits) + { + return URShift(number, (int)bits); + } + } + + internal abstract class GeneralDigest : IDigest + { + private const int ByteLength = 64; + private readonly byte[] XBuf; + private int XBufOff; + private long ByteCount; + + public abstract string AlgorithmName { get; } + + protected GeneralDigest() + { + XBuf = new byte[4]; + } + + protected GeneralDigest(GeneralDigest t) + { + XBuf = new byte[t.XBuf.Length]; + Array.Copy(t.XBuf, 0, XBuf, 0, t.XBuf.Length); + + XBufOff = t.XBufOff; + ByteCount = t.ByteCount; + } + + public void Update(byte input) + { + XBuf[XBufOff++] = input; + + if (XBufOff == XBuf.Length) + { + ProcessWord(XBuf, 0); + XBufOff = 0; + } + + ByteCount++; + } + + public void BlockUpdate(byte[] input, int inOff, int length) + { + //更新当前消息摘要 + while ((XBufOff != 0) && (length > 0)) + { + Update(input[inOff]); + inOff++; + length--; + } + + //处理完整的消息摘要 + while (length > XBuf.Length) + { + ProcessWord(input, inOff); + + inOff += XBuf.Length; + length -= XBuf.Length; + ByteCount += XBuf.Length; + } + + //填充剩余的消息摘要 + while (length > 0) + { + Update(input[inOff]); + + inOff++; + length--; + } + } + + public void Finish() + { + long bitLength = (ByteCount << 3); + + //添加字节 + Update(unchecked((byte)128)); + + while (XBufOff != 0) Update(unchecked((byte)0)); + ProcessLength(bitLength); + ProcessBlock(); + } + + public virtual void Reset() + { + ByteCount = 0; + XBufOff = 0; + Array.Clear(XBuf, 0, XBuf.Length); + } + + public int GetByteLength() + { + return ByteLength; + } + + internal abstract void ProcessWord(byte[] input, int inOff); + + internal abstract void ProcessLength(long bitLength); + + internal abstract void ProcessBlock(); + + public abstract int GetDigestSize(); + + public abstract int DoFinal(byte[] output, int outOff); + } + + internal sealed class SM3Digest : GeneralDigest + { + private const int DIGEST_LENGTH = 32; + private const int T_1 = 0x79cc4519; + private const int T_2 = 0x7a879d8a; + + private static readonly int[] IV = new int[] { 0x7380166f, 0x4914b2b9, 0x172442d7, unchecked((int)0xda8a0600), unchecked((int)0xa96f30bc), 0x163138aa, unchecked((int)0xe38dee4d), unchecked((int)0xb0fb0e4e) }; + private static readonly int[] X0 = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private readonly int[] _v1 = new int[8]; + private readonly int[] _v2 = new int[8]; + private readonly int[] _x = new int[68]; + private int _xOff; + + public override string AlgorithmName { get { return "SM3"; } } + + public SM3Digest() + : base() + { + Reset(); + } + + public SM3Digest(SM3Digest t) + : base(t) + { + Array.Copy(t._x, 0, _x, 0, t._x.Length); + _xOff = t._xOff; + Array.Copy(t._v1, 0, _v1, 0, t._v1.Length); + } + + private static void ConvertIntegerToBigEndian(int num, byte[] bytes, int offset) + { + bytes[offset] = (byte)(BitOperator.URShift(num, 24)); + bytes[++offset] = (byte)(BitOperator.URShift(num, 16)); + bytes[++offset] = (byte)(BitOperator.URShift(num, 8)); + bytes[++offset] = (byte)(num); + } + + private static int Rotate(int x, int n) + { + return (x << n) | (BitOperator.URShift(x, (32 - n))); + } + + private static int P0(int x) + { + return (x) ^ Rotate(x, 9) ^ Rotate(x, 17); + } + + private static int P1(int x) + { + return (x) ^ Rotate(x, 15) ^ Rotate(x, 23); + } + + private static int FFOne(int x, int y, int z) + { + return (x ^ y ^ z); + } + + private static int FFSecond(int x, int y, int z) + { + return ((x & y) | (x & z) | (y & z)); + } + + private static int GGOne(int x, int y, int z) + { + return (x ^ y ^ z); + } + + private static int GGSecond(int x, int y, int z) + { + return ((x & y) | (~x & z)); + } + + public override void Reset() + { + base.Reset(); + + Array.Copy(IV, 0, _v1, 0, IV.Length); + + _xOff = 0; + Array.Copy(X0, 0, _x, 0, X0.Length); + } + + internal override void ProcessWord(byte[] input, int inOff) + { + int n = input[inOff] << 24; + n |= (input[++inOff] & 0xff) << 16; + n |= (input[++inOff] & 0xff) << 8; + n |= (input[++inOff] & 0xff); + _x[_xOff] = n; + + if (++_xOff == 16) + { + ProcessBlock(); + } + } + + internal override void ProcessLength(long bitLength) + { + if (_xOff > 14) + { + ProcessBlock(); + } + + _x[14] = (int)(BitOperator.URShift(bitLength, 32)); + _x[15] = (int)(bitLength & unchecked((int)0xffffffff)); + } + + internal override void ProcessBlock() + { + int j; + + int[] ww = _x; + int[] ww_ = new int[64]; + + for (j = 16; j < 68; j++) + { + ww[j] = P1(ww[j - 16] ^ ww[j - 9] ^ (Rotate(ww[j - 3], 15))) ^ (Rotate(ww[j - 13], 7)) ^ ww[j - 6]; + } + for (j = 0; j < 64; j++) + { + ww_[j] = ww[j] ^ ww[j + 4]; + } + + int[] vv = _v1; + int[] vv_ = _v2; + + Array.Copy(vv, 0, vv_, 0, IV.Length); + + int SS1, SS2, TT1, TT2; + int aaa; + + for (j = 0; j < 16; j++) + { + aaa = Rotate(vv_[0], 12); + SS1 = aaa + vv_[4] + Rotate(T_1, j); + SS1 = Rotate(SS1, 7); + SS2 = SS1 ^ aaa; + + TT1 = FFOne(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[j]; + TT2 = GGOne(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[j]; + + vv_[3] = vv_[2]; + vv_[2] = Rotate(vv_[1], 9); + vv_[1] = vv_[0]; + vv_[0] = TT1; + vv_[7] = vv_[6]; + vv_[6] = Rotate(vv_[5], 19); + vv_[5] = vv_[4]; + vv_[4] = P0(TT2); + } + + for (j = 16; j < 64; j++) + { + aaa = Rotate(vv_[0], 12); + SS1 = aaa + vv_[4] + Rotate(T_2, j); + SS1 = Rotate(SS1, 7); + SS2 = SS1 ^ aaa; + + TT1 = FFSecond(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[j]; + TT2 = GGSecond(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[j]; + + vv_[3] = vv_[2]; + vv_[2] = Rotate(vv_[1], 9); + vv_[1] = vv_[0]; + vv_[0] = TT1; + vv_[7] = vv_[6]; + vv_[6] = Rotate(vv_[5], 19); + vv_[5] = vv_[4]; + vv_[4] = P0(TT2); + } + + for (j = 0; j < 8; j++) + { + vv[j] ^= vv_[j]; + } + + _xOff = 0; + Array.Copy(X0, 0, _x, 0, X0.Length); + } + + public override int GetDigestSize() + { + return DIGEST_LENGTH; + } + + public override int DoFinal(byte[] output, int outOff) + { + Finish(); + for (int i = 0; i < 8; i++) + { + ConvertIntegerToBigEndian(_v1[i], output, outOff + i * 4); + } + Reset(); + return DIGEST_LENGTH; + } + } + + /// + /// 获取 SM3 哈希值。 + /// + /// 信息字节数组。 + /// 哈希字节数组。 + public static byte[] Hash(byte[] bytes) + { + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + + SM3Digest sm3 = new SM3Digest(); + sm3.BlockUpdate(bytes, 0, bytes.Length); + byte[] hashBytes = new byte[sm3.GetDigestSize()]; + sm3.DoFinal(hashBytes, 0); + return hashBytes; + } + + /// + /// 获取 SM3 哈希值。 + /// + /// 文本信息。 + /// 哈希值。 + public static string Hash(string message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + + byte[] msgBytes = Encoding.UTF8.GetBytes(message); + byte[] hashBytes = Hash(msgBytes); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM3UtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM3UtilityTests.cs new file mode 100644 index 00000000..edaf878a --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM3UtilityTests.cs @@ -0,0 +1,18 @@ +using System; +using Xunit; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests +{ + public class TestCase_SM3UtilityTests + { + [Fact(DisplayName = "测试用例:计算 SM3 哈希值")] + public void TestSM3Hash() + { + byte[] msgBytes = Convert.FromBase64String("QXdlc29tZSBTS0lULkZsdXJsSHR0cENsaWVudC5XZWNoYXQuVGVucGF5VjMh"); + string expectedHashText = "3F5EC5A79871755905E34013FD86D906B334281D2CA5B9FF612A136C7C377815"; + string actualHashText = BitConverter.ToString(Utilities.SM3Utility.Hash(msgBytes)).Replace("-", ""); + + Assert.Equal(actualHashText, expectedHashText, ignoreCase: true); + } + } +}