From 2c377451130b5b236b3441ab84dd69fdd267e654 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 10 Nov 2022 01:57:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(tenpayv3):=20=E6=96=B0=E5=A2=9E=20SM4=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/AESUtility.cs | 42 +- .../Utilities/RSAUtility.cs | 54 +- .../Utilities/SM2Utility.cs | 599 ++++++++++++++++++ .../Utilities/SM4Utility.cs | 78 +++ .../TestCase_AESUtilityTests.cs | 8 +- .../TestCase_SM4UtilityTests.cs | 22 + 6 files changed, 755 insertions(+), 48 deletions(-) create mode 100644 src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM4Utility.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM4UtilityTests.cs diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs index d6f73f58..d2c28b51 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs @@ -18,26 +18,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 基于 GCM 模式解密数据。 /// /// AES 密钥字节数组。 - /// 加密使用的初始化向量字节数组。 + /// 加密使用的初始化向量字节数组。 /// 加密使用的附加数据包字节数组。 /// 待解密数据字节数组。 + /// 填充模式。(默认值:) /// 解密后的数据字节数组。 - public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] ivBytes, byte[] aadBytes, byte[] cipherBytes) + public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = AES_CIPHER_PADDING_NOPADDING) { - if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); - if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes)); - if (aadBytes == null) throw new ArgumentNullException(nameof(aadBytes)); - if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); + const int KEY_LENGTH_BYTE = 32; + const int NONCE_LENGTH_BYTE = 12; + const int TAG_LENGTH_BYTE = 16; - const int TAG_LENGTH_BIT = 128; - IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", AES_CIPHER_ALGORITHM_GCM, AES_CIPHER_PADDING_NOPADDING)); - ICipherParameters aeadParams = new AeadParameters( + if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); + if (keyBytes.Length != KEY_LENGTH_BYTE) throw new ArgumentException($"Invalid key byte length (expected: {KEY_LENGTH_BYTE}, actual: {keyBytes.Length}).", nameof(keyBytes)); + if (nonceBytes == null) throw new ArgumentNullException(nameof(nonceBytes)); + if (nonceBytes.Length != NONCE_LENGTH_BYTE) throw new ArgumentException($"Invalid nonce byte length (expected: {NONCE_LENGTH_BYTE}, actual: {nonceBytes.Length}).", nameof(nonceBytes)); + if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); + if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes)); + + IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", AES_CIPHER_ALGORITHM_GCM, paddingMode)); + ICipherParameters cipherParams = new AeadParameters( new KeyParameter(keyBytes), - TAG_LENGTH_BIT, - ivBytes, + TAG_LENGTH_BYTE * 8, + nonceBytes, aadBytes ); - cipher.Init(false, aeadParams); + cipher.Init(false, cipherParams); byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)]; int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0); cipher.DoFinal(plainBytes, len); @@ -48,21 +54,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 基于 GCM 模式解密数据。 /// /// AES 密钥。 - /// 加密使用的初始化向量。 + /// 加密使用的初始化向量。 /// 加密使用的附加数据包。 /// 经 Base64 编码后的待解密数据。 + /// 填充模式。(默认值:) /// 解密后的文本数据。 - public static string DecryptWithGCM(string key, string iv, string? aad, string cipherText) + public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = AES_CIPHER_PADDING_NOPADDING) { if (key == null) throw new ArgumentNullException(nameof(key)); - if (iv == null) throw new ArgumentNullException(nameof(iv)); + if (nonce == null) throw new ArgumentNullException(nameof(nonce)); if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); byte[] plainBytes = DecryptWithGCM( keyBytes: Encoding.UTF8.GetBytes(key), - ivBytes: Encoding.UTF8.GetBytes(iv), + nonceBytes: Encoding.UTF8.GetBytes(nonce), aadBytes: Encoding.UTF8.GetBytes(aad ?? string.Empty), - cipherBytes: Convert.FromBase64String(cipherText) + cipherBytes: Convert.FromBase64String(cipherText), + paddingMode: paddingMode ); return Encoding.UTF8.GetString(plainBytes); } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs index f0a06a40..e0c2af24 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs @@ -54,19 +54,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return (RsaKeyParameters)cert.GetPublicKey(); } - private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] plainBytes) + private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes) { ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256); signer.Init(true, rsaPrivateKeyParams); - signer.BlockUpdate(plainBytes, 0, plainBytes.Length); + signer.BlockUpdate(msgBytes, 0, msgBytes.Length); return signer.GenerateSignature(); } - private static bool VerifyWithSHA256(RsaKeyParameters rsaPublicKeyParams, byte[] plainBytes, byte[] signBytes) + private static bool VerifyWithSHA256(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, byte[] signBytes) { ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256); signer.Init(false, rsaPublicKeyParams); - signer.BlockUpdate(plainBytes, 0, plainBytes.Length); + signer.BlockUpdate(msgBytes, 0, msgBytes.Length); return signer.VerifySignature(signBytes); } @@ -88,31 +88,31 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 使用私钥基于 SHA-256 算法生成签名。 /// /// PKCS#8 私钥字节数组。 - /// 待签名的数据字节数组。 + /// 待签名的数据字节数组。 /// 签名字节数组。 - public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] plainBytes) + public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] msgBytes) { if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); - if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - return SignWithSHA256(rsaKeyParams, plainBytes); + return SignWithSHA256(rsaKeyParams, msgBytes); } /// /// 使用私钥基于 SHA-256 算法生成签名。 /// /// PKCS#8 私钥(PEM 格式)。 - /// 待签名的文本数据。 + /// 待签名的文本数据。 /// 经 Base64 编码的签名。 - public static string SignWithSHA256(string privateKey, string plainText) + public static string SignWithSHA256(string privateKey, string message) { if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); - if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + if (message == null) throw new ArgumentNullException(nameof(message)); byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); - byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); - byte[] signBytes = SignWithSHA256(privateKeyBytes, plainBytes); + byte[] msgBytes = Encoding.UTF8.GetBytes(message); + byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes); return Convert.ToBase64String(signBytes); } @@ -120,55 +120,55 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#8 公钥字节数据。 - /// 待验证的数据字节数据。 + /// 待验证的数据字节数据。 /// 待验证的签名字节数据。 /// 验证结果。 - public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes) + public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes) { if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); - if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); if (signBytes == null) throw new ArgumentNullException(nameof(signBytes)); RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); - return VerifyWithSHA256(rsaKeyParams, plainBytes, signBytes); + return VerifyWithSHA256(rsaKeyParams, msgBytes, signBytes); } /// /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#8 公钥(PEM 格式)。 - /// 待验证的文本数据。 + /// 待验证的文本数据。 /// 经 Base64 编码的待验证的签名。 /// 验证结果。 - public static bool VerifyWithSHA256(string publicKey, string plainText, string signature) + public static bool VerifyWithSHA256(string publicKey, string message, string signature) { if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); - if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + if (message == null) throw new ArgumentNullException(nameof(message)); if (signature == null) throw new ArgumentNullException(nameof(signature)); byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); - byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = Convert.FromBase64String(signature); - return VerifyWithSHA256(publicKeyBytes, plainBytes, signBytes); + return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes); } /// /// 使用证书基于 SHA-256 算法验证签名。 /// /// 证书(PEM 格式)。 - /// 待验证的文本数据。 + /// 待验证的文本数据。 /// 经 Base64 编码的待验证的签名。 /// 验证结果。 - public static bool VerifyWithSHA256ByCertificate(string certificate, string plainText, string signature) + public static bool VerifyWithSHA256ByCertificate(string certificate, string message, string signature) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + if (message == null) throw new ArgumentNullException(nameof(message)); if (signature == null) throw new ArgumentNullException(nameof(signature)); RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate); - byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = Convert.FromBase64String(signature); - return VerifyWithSHA256(rsaKeyParams, plainBytes, signBytes); + return VerifyWithSHA256(rsaKeyParams, msgBytes, signBytes); } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs new file mode 100644 index 00000000..a48a31d9 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs @@ -0,0 +1,599 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities +{ + /// + /// SM2 算法工具类。 + /// + public static partial class SM2Utility + { + // 默认用户身份标识:1234567812345678 + private static readonly byte[] DEFAULT_USERID_BYTES = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; + + private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey) + { + privateKey = privateKey + .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) + .Replace("-----END PRIVATE KEY-----", string.Empty); + privateKey = Regex.Replace(privateKey, "\\s+", string.Empty); + return Convert.FromBase64String(privateKey); + } + + private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey) + { + publicKey = publicKey + .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) + .Replace("-----END PUBLIC KEY-----", string.Empty); + publicKey = Regex.Replace(publicKey, "\\s+", string.Empty); + return Convert.FromBase64String(publicKey); + } + + private static X509Certificate ParseX509Certificate(string certificate) + { + using (TextReader sreader = new StringReader(certificate)) + { + PemReader pemReader = new PemReader(sreader); + return (X509Certificate)pemReader.ReadObject(); + } + } + + private static ECPublicKeyParameters ConvertCertificateToPublicKeyParams(string certificate) + { + X509Certificate cert = ParseX509Certificate(certificate); + return (ECPublicKeyParameters)cert.GetPublicKey(); + } + + private static byte[] SignWithSM3(ECPrivateKeyParameters ecPrivateKeyParams, byte[] userIdBytes, byte[] plainBytes) + { + ISigner signer = new SM2Signer(new SM3Digest()); + ICipherParameters idParams = new ParametersWithID(new ParametersWithRandom(ecPrivateKeyParams), userIdBytes); + signer.Init(true, idParams); + signer.BlockUpdate(plainBytes, 0, plainBytes.Length); + return signer.GenerateSignature(); + } + + private static bool VerifyWithSM3(ECPublicKeyParameters ecPublicKeyParams, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes) + { + ISigner signer = new SM2Signer(new SM3Digest()); + ICipherParameters idParams = new ParametersWithID(new ParametersWithRandom(ecPublicKeyParams), userIdBytes); + signer.Init(false, idParams); + signer.BlockUpdate(plainBytes, 0, plainBytes.Length); + return signer.VerifySignature(signBytes); + } + + /// + /// 使用私钥基于 SM3 算法生成签名。 + /// + /// PKCS#8 私钥字节数组。 + /// 待签名的数据字节数组。 + /// 签名字节数组。 + public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] plainBytes) + { + return SignWithSM3( + privateKeyBytes: privateKeyBytes, + userIdBytes: DEFAULT_USERID_BYTES, + plainBytes: plainBytes + ); + } + + /// + /// 使用私钥基于 SM3 算法生成签名。 + /// + /// PKCS#8 私钥字节数组。 + /// 用户身份标识字节数组。 + /// 待签名的数据字节数组。 + /// 签名字节数组。 + public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] userIdBytes, byte[] plainBytes) + { + if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); + if (userIdBytes == null) throw new ArgumentNullException(nameof(userIdBytes)); + if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + + //ECPrivateKeyParameters ecKeyParams = (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); + //return SignWithSM3(ecKeyParams, userIdBytes, plainBytes); + + BigInteger userD = new BigInteger(1, privateKeyBytes); + SM2Factory sm2Factory = new SM2Factory(); + + ECPoint userKey = sm2Factory.ecc_point_g.Multiply(userD); + SM3Digest sm3Digest = new SM3Digest(); + var z = sm2Factory.Sm2GetZ(userIdBytes, userKey); + sm3Digest.BlockUpdate(z, 0, z.Length); + sm3Digest.BlockUpdate(plainBytes, 0, plainBytes.Length); + var md = new byte[32]; + sm3Digest.DoFinal(md, 0); + + var result = sm2Factory.Sm2Sign(md, userD, userKey); + return result.ToByteArray(); + } + + /// + /// 使用私钥基于 SM3 算法生成签名。 + /// + /// PKCS#8 私钥(PEM 格式)。 + /// 待签名的文本数据。 + /// 经 Base64 编码的签名。 + public static string SignWithSM3(string privateKey, string plainText) + { + if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); + if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + + byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); + byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + byte[] signBytes = SignWithSM3(privateKeyBytes, plainBytes); + return Convert.ToBase64String(signBytes); + } + + /// + /// 使用公钥基于 SM3 算法验证签名。 + /// + /// PKCS#8 公钥字节数据。 + /// 待验证的数据字节数据。 + /// 待验证的签名字节数据。 + /// 验证结果。 + public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes) + { + return VerifyWithSM3( + publicKeyBytes: publicKeyBytes, + userIdBytes: DEFAULT_USERID_BYTES, + plainBytes: plainBytes, + signBytes: signBytes + ); + } + + /// + /// 使用公钥基于 SM3 算法验证签名。 + /// + /// PKCS#8 公钥字节数据。 + /// 用户身份标识字节数组。 + /// 待验证的数据字节数据。 + /// 待验证的签名字节数据。 + /// 验证结果。 + public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes) + { + if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); + if (userIdBytes == null) throw new ArgumentNullException(nameof(userIdBytes)); + if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + if (signBytes == null) throw new ArgumentNullException(nameof(signBytes)); + + ECPublicKeyParameters ecPublicKeyParams = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); + return VerifyWithSM3(ecPublicKeyParams, userIdBytes, plainBytes, signBytes); + } + + /// + /// 使用公钥基于 SM3 算法验证签名。 + /// + /// PKCS#8 公钥(PEM 格式)。 + /// 待验证的文本数据。 + /// 经 Base64 编码的待验证的签名。 + /// 验证结果。 + public static bool VerifyWithSM3(string publicKey, string plainText, string signature) + { + if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); + if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + if (signature == null) throw new ArgumentNullException(nameof(signature)); + + byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); + byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + byte[] signBytes = Convert.FromBase64String(signature); + return VerifyWithSM3(publicKeyBytes, plainBytes, signBytes); + } + + /// + /// 从 CRT/CER 证书中导出 PKCS#8 公钥。 + /// + /// 即从 -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- + /// 转为 -----BEGIN PUBLIC KEY----- ..... -----END PUBLIC KEY----- + /// + /// + /// 证书(PEM 格式)。 + /// PKCS#8 公钥(PEM 格式)。 + public static string ExportPublicKey(string certificate) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + using (TextWriter swriter = new StringWriter()) + { + ECKeyParameters ecKeyParams = ConvertCertificateToPublicKeyParams(certificate); + PemWriter pemWriter = new PemWriter(swriter); + pemWriter.WriteObject(ecKeyParams); + pemWriter.Writer.Flush(); + return swriter.ToString()!; + } + } + + /// + /// 从 CRT/CER 证书中导出证书序列号。 + /// + /// 证书(PEM 格式)。 + /// 证书序列号。 + public static string ExportSerialNumber(string certificate) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + X509Certificate cert = ParseX509Certificate(certificate); + return cert.SerialNumber.ToString(16); + } + + /// + /// 从 CRT/CER 证书中导出证书颁发时间。 + /// + /// 证书(PEM 格式)。 + /// 证书颁发时间。 + public static DateTimeOffset ExportEffectiveTime(string certificate) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + X509Certificate cert = ParseX509Certificate(certificate); + return new DateTimeOffset(cert.NotBefore); + } + + /// + /// 从 CRT/CER 证书中导出证书过期时间。 + /// + /// 证书(PEM 格式)。 + /// 证书过期时间。 + public static DateTimeOffset ExportExpireTime(string certificate) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + X509Certificate cert = ParseX509Certificate(certificate); + return new DateTimeOffset(cert.NotAfter); + } + } + + partial class SM2Utility + { + internal sealed class Cipher + { + private int ct = 1; + + private ECPoint? p2; + private SM3Digest? sm3keybase; + private SM3Digest? sm3c3; + + private byte[] key = new byte[32]; + private byte keyOff = 0; + + private void Reset() + { + sm3keybase = new SM3Digest(); + sm3c3 = new SM3Digest(); + + + byte[] p; + + + p = p2.Normalize().XCoord.ToBigInteger().ToByteArray32(); + + sm3keybase.BlockUpdate(p, 0, p.Length); + sm3c3.BlockUpdate(p, 0, p.Length); + + + p = p2.Normalize().YCoord.ToBigInteger().ToByteArray32(); + + sm3keybase.BlockUpdate(p, 0, p.Length); + + + ct = 1; + NextKey(); + } + + private void NextKey() + { + + SM3Digest sm3keycur = new SM3Digest(sm3keybase); + sm3keycur.Update((byte)(ct >> 24 & 0xff)); + sm3keycur.Update((byte)(ct >> 16 & 0xff)); + sm3keycur.Update((byte)(ct >> 8 & 0xff)); + sm3keycur.Update((byte)(ct & 0xff)); + sm3keycur.DoFinal(key, 0); + var testKey = HexUtility.ConvertBytesToHex(key); + + keyOff = 0; + ct++; + } + + public ECPoint Init_enc(SM2Factory sm2, ECPoint userKey) + { + BigInteger k = null; + ECPoint c1 = null; + + + AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.GenerateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)key.Private; + ECPublicKeyParameters ecpub = (ECPublicKeyParameters)key.Public; + k = ecpriv.D; + c1 = ecpub.Q; + + + p2 = userKey.Multiply(k); + Reset(); + + + return c1; + } + + public void Encrypt(byte[] data) + { + sm3c3.BlockUpdate(data, 0, data.Length); + for (int i = 0; i < data.Length; i++) + { + if (keyOff == key.Length) + NextKey(); + + + data[i] ^= key[keyOff++]; + } + } + + public void Init_dec(BigInteger userD, ECPoint c1) + { + p2 = c1.Multiply(userD); + Reset(); + } + + public void Decrypt(byte[] data) + { + + for (int i = 0; i < data.Length; i++) + { + if (keyOff == key.Length) + NextKey(); + + + data[i] ^= key[keyOff++]; + } + sm3c3.BlockUpdate(data, 0, data.Length); + } + + public void Dofinal(byte[] c3) + { + byte[] p = p2.Normalize().YCoord.ToBigInteger().ToByteArray32(); + sm3c3.BlockUpdate(p, 0, p.Length); + sm3c3.DoFinal(c3, 0); + Reset(); + } + } + + internal sealed class SM2Factory + { + public static readonly string[] SM2_PARAMS = { + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p,0 + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a,1 + "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b,2 + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n,3 + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx,4 + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" // gy,5 + }; + + public string[] ecc_param = SM2_PARAMS; + + public readonly BigInteger ecc_p; + public readonly BigInteger ecc_a; + public readonly BigInteger ecc_b; + public readonly BigInteger ecc_n; + public readonly BigInteger ecc_gx; + public readonly BigInteger ecc_gy; + + public readonly ECCurve ecc_curve; + public readonly ECPoint ecc_point_g; + + public readonly ECDomainParameters ecc_bc_spec; + + public readonly ECKeyPairGenerator ecc_key_pair_generator; + + public SM2Factory() + { + ecc_param = SM2_PARAMS; + + ECFieldElement ecc_gx_fieldelement; + ECFieldElement ecc_gy_fieldelement; + + ecc_p = new BigInteger(ecc_param[0], 16); + ecc_a = new BigInteger(ecc_param[1], 16); + ecc_b = new BigInteger(ecc_param[2], 16); + ecc_n = new BigInteger(ecc_param[3], 16); + ecc_gx = new BigInteger(ecc_param[4], 16); + ecc_gy = new BigInteger(ecc_param[5], 16); + + + ecc_gx_fieldelement = new FpFieldElement(ecc_p, ecc_gx); + ecc_gy_fieldelement = new FpFieldElement(ecc_p, ecc_gy); + + ecc_curve = new FpCurve(ecc_p, ecc_a, ecc_b); + ecc_point_g = new FpPoint(ecc_curve, ecc_gx_fieldelement, ecc_gy_fieldelement); + + ecc_bc_spec = new ECDomainParameters(ecc_curve, ecc_point_g, ecc_n); + + ECKeyGenerationParameters ecc_ecgenparam; + ecc_ecgenparam = new ECKeyGenerationParameters(ecc_bc_spec, new SecureRandom()); + + ecc_key_pair_generator = new ECKeyPairGenerator(); + ecc_key_pair_generator.Init(ecc_ecgenparam); + } + + public SM2Signature Sm2Sign(byte[] md, BigInteger userD, ECPoint userKey) + { + BigInteger e = new BigInteger(1, md); + BigInteger k = null; + ECPoint kp = null; + BigInteger r = null; + BigInteger s = null; + do + { + do + { + // 正式环境 + AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.GenerateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)keypair.Private; + ECPublicKeyParameters ecpub = (ECPublicKeyParameters)keypair.Public; + k = ecpriv.D; + kp = ecpub.Q; + //System.out.println("BigInteger:" + k + "\nECPoint:" + kp); + + //System.out.println("计算曲线点X1: "+ kp.getXCoord().toBigInteger().toString(16)); + //System.out.println("计算曲线点Y1: "+ kp.getYCoord().toBigInteger().toString(16)); + //System.out.println(""); + // r + r = e.Add(kp.XCoord.ToBigInteger()); + r = r.Mod(this.ecc_n); + } while (r.Equals(BigInteger.Zero) || r.Add(k).Equals(this.ecc_n) || r.ToString(16).Length != 64); + + // (1 + dA)~-1 + BigInteger da_1 = userD.Add(BigInteger.One); + da_1 = da_1.ModInverse(this.ecc_n); + // s + s = r.Multiply(userD); + s = k.Subtract(s).Mod(this.ecc_n); + s = da_1.Multiply(s).Mod(this.ecc_n); + } while (s.Equals(BigInteger.Zero) || s.ToString(16).Length != 64); + var sM2Signature = new SM2Signature + { + R = r.ToByteArray32(), + S = s.ToByteArray32() + }; + return sM2Signature; + } + + public SM2Result Sm2Verify(byte[] md, ECPoint userKey, BigInteger r, BigInteger s) + { + var sm2Result = new SM2Result(); + + BigInteger e = new BigInteger(1, md); + BigInteger t = r.Add(s).Mod(this.ecc_n); + if (t.Equals(BigInteger.Zero)) + { + return sm2Result; + } + else + { + ECPoint x1y1 = ecc_point_g.Multiply(s); + //System.out.println("计算曲线点X0: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16)); + //System.out.println("计算曲线点Y0: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16)); + //System.out.println(""); + + x1y1 = x1y1.Add(userKey.Multiply(t)); + //System.out.println("计算曲线点X1: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16)); + //System.out.println("计算曲线点Y1: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16)); + //System.out.println(""); + sm2Result.R = e.Add(x1y1.Normalize().XCoord.ToBigInteger()).Mod(this.ecc_n); + //System.out.println("R: " + sm2Result.R.toString(16)); + return sm2Result; + } + } + + public byte[] Sm2GetZ(byte[] userId, ECPoint userKey) + { + SM3Digest sm3 = new SM3Digest(); + byte[] p; + // userId length + int len = userId.Length * 8; + sm3.Update((byte)(len >> 8 & 0x00ff)); + sm3.Update((byte)(len & 0x00ff)); + + // userId + sm3.BlockUpdate(userId, 0, userId.Length); + + // a,b + p = ecc_a.ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + p = ecc_b.ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + // gx,gy + p = ecc_gx.ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + p = ecc_gy.ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + + // x,y + p = userKey.Normalize().XCoord.ToBigInteger().ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + p = userKey.Normalize().YCoord.ToBigInteger().ToByteArray32(); + sm3.BlockUpdate(p, 0, p.Length); + + // Z + byte[] md = new byte[sm3.GetDigestSize()]; + sm3.DoFinal(md, 0); + + return md; + } + } + + internal sealed class SM2Signature + { + public byte[] R { get; set; } + + public byte[] S { get; set; } + + public SM2Signature() + { + R = Array.Empty(); + S = Array.Empty(); + } + + public SM2Signature(byte[] signBytes) + : this() + { + if (signBytes.Length == 65) + { + signBytes = signBytes.Skip(1).ToArray(); + } + + if (signBytes.Length == 64) + { + R = signBytes.Take(32).ToArray(); + S = signBytes.Skip(32).ToArray(); + } + else + { + throw new ArgumentException("Invalid signature data.", nameof(signBytes)); + } + } + + public byte[] ToByteArray() + { + return R.Concat(S).ToArray(); + } + + public byte[] ToByteArray04() + { + byte[] temp = new byte[65]; + temp[0] = 0x04; + + Array.Copy(this.R, 0, temp, 1, 32); + Array.Copy(this.S, 0, temp, 33, 32); + + return temp; + } + } + + internal sealed class SM2Result + { + public BigInteger R; + public BigInteger S; + + public byte[] sa; + public byte[] sb; + public byte[] s1; + public byte[] s2; + + public ECPoint keyra; + public ECPoint keyrb; + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM4Utility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM4Utility.cs new file mode 100644 index 00000000..df8a918e --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM4Utility.cs @@ -0,0 +1,78 @@ +using System; +using System.Text; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities +{ + /// + /// SM4 算法工具类。 + /// + public static class SM4Utility + { + private const string SM4_CIPHER_ALGORITHM_GCM = "SM4/GCM"; + private const string SM4_CIPHER_PADDING_NOPADDING = "NoPadding"; + + /// + /// 基于 GCM 模式解密数据。 + /// + /// SM4 密钥字节数组。 + /// 加密使用的初始化向量字节数组。 + /// 加密使用的附加数据包字节数组。 + /// 待解密数据字节数组。 + /// 填充模式。(默认值:) + /// 解密后的数据字节数组。 + public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_NOPADDING) + { + const int KEY_LENGTH_BYTE = 16; + const int NONCE_LENGTH_BYTE = 12; + const int TAG_LENGTH_BYTE = 16; + + if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); + if (keyBytes.Length != KEY_LENGTH_BYTE) throw new ArgumentException($"Invalid key byte length (expected: {KEY_LENGTH_BYTE}, actual: {keyBytes.Length}).", nameof(keyBytes)); + if (nonceBytes == null) throw new ArgumentNullException(nameof(nonceBytes)); + if (nonceBytes.Length != NONCE_LENGTH_BYTE) throw new ArgumentException($"Invalid nonce byte length (expected: {NONCE_LENGTH_BYTE}, actual: {nonceBytes.Length}).", nameof(nonceBytes)); + if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); + if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes)); + + IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", SM4_CIPHER_ALGORITHM_GCM, paddingMode)); + ICipherParameters cipherParams = new AeadParameters( + new KeyParameter(keyBytes), + TAG_LENGTH_BYTE * 8, + nonceBytes, + aadBytes + ); + cipher.Init(false, cipherParams); + byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)]; + int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0); + cipher.DoFinal(plainBytes, len); + return plainBytes; + } + + /// + /// 基于 GCM 模式解密数据。 + /// + /// SM4 密钥。 + /// 加密使用的初始化向量。 + /// 加密使用的附加数据包。 + /// 经 Base64 编码后的待解密数据。 + /// 填充模式。(默认值:) + /// 解密后的文本数据。 + public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = SM4_CIPHER_PADDING_NOPADDING) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (nonce == null) throw new ArgumentNullException(nameof(nonce)); + if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); + + byte[] plainBytes = DecryptWithGCM( + keyBytes: Encoding.UTF8.GetBytes(key), + nonceBytes: Encoding.UTF8.GetBytes(nonce), + aadBytes: Encoding.UTF8.GetBytes(aad ?? string.Empty), + cipherBytes: Convert.FromBase64String(cipherText), + paddingMode: paddingMode + ); + return Encoding.UTF8.GetString(plainBytes); + } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_AESUtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_AESUtilityTests.cs index e2519144..0de853bd 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_AESUtilityTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_AESUtilityTests.cs @@ -1,4 +1,4 @@ -using Xunit; +using Xunit; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests { @@ -8,11 +8,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests public void TestAESGCMDecrypt() { string key = "f09b03a7a1902b5b4913856f1fd07ab1"; - string iv = "aae8c2e79c5b"; + string nonce = "aae8c2e79c5b"; string aad = "certificate"; string cipherText = "x9kkL5w1JuaypcjhrYIP+kVNlN8o8uN4yJyJjy5lg+PyPnQL2Zn//ORaXAyzdaK/WBMVd3u/Y9hLaTBLMyRXzowsrkJ5PT37johye48N7BAJQ0PJwW++d1RdhOOPjoqfmws6rSV5Gv2qhfdKjmpxVjr8xr71dtBt8J2wu+bAV99HHQoAynm/Pp9OQYZgpOQ+1cyFHd43TAxOoFfmixKrXr3HP8lJot0XCUSq4qkr1Hs44FV2KuzntSk8eqKr5N17UcuPF3VYnnnF/AvQ7HuLKWwrHhUbaXfkwy0Q2n36UJMfBj7344S97E8BnS89ojgOPQi+olBPyNgrtDWHgsJAKu7HA6PV/FgmXcrZirje/AH1u25es4z5xItHscm//6rDvALgf7greV5OJzMsSl/KVDtbkjDSzim0j4ZTduIfzh7l6jfOz115ITcNILT9ef2KkcMyBBc89GZlMGeHTbgsBHzGeLawX3dXFjqt5aMnHM3VWCovA2aUM4c//rqkZGf+Va86OEFoJQLiSTFpkOrKxcIxcrbKPLTgiDWRT3wzmnUDg7kSPbluzt3ROvMFq9lB8bO/pBd7TD2w87sfUdKLj69FniX6s37SBeRVhw8GSIvBf5lpLUhqL5zKYlbuAWePuz0wVV4NTtkaVKZlmm8KTODyZDFpsyKPubDDcwT1ftRf5aSVM4x04I/1B7GkNz/TOy+zpJ0h0B7VHdxyO5JYiI/1qsatX/FE2aJdQYMYOtqfDH7ZH5UUKIqo538OKvE2M4MlBR/aVE4z4QDKfE/1kYrOfvVGfDzF/FWHfUrcqB8kdQMvk8vCoM8yYZsX4KE1aoJbNM2pWv2tpr9JE8b/VQgUyHOgPYAha+UOmZki4Sfl9H62687EIWdbM57ZxmwIiBp60SrJLiBfZon9JqXKdtJOKj0CRokQiBnONNXCVerLFeBNQfeKRw8tgJXf+QPohMGYkSDdc8hTgdbmhTwB1Vv01stlYK12QMNRCovlp/fcmpB72Phlq+/3p5pqMzknw+qm5QAz7JnpZJCFHit52gHwAkKRkVPB3HF2rfLrdTYz5c9Bok1ICAY9My47eLFdduIe99V52cjMLQuUmNFBPrDdyZKVqIHJ/wtWO/wIFpAVGSJMHctyEKmeJVc1IQN74Wm00PrpPackHdO3G41bBmkp5pqUdsSgSkwdfNVqv0cMcSe04NrRGNKMcZ7TA/CMaP1YnhxvVE+z8aksJqSJ+gdplvuwl40y5C8UEHeAi1V8Q0Bf4YvYRgOVIWm2Lzjdn2z9PWLGcStUj11/1hthk2li4V3mgm2Cr2IZme2sn4rZmJ6dexGP1nk+ZYOq0xLE8F7oex9gyDN+A/6zHqnuhO/X08qye0gochMr8U89Qvj2c0L3P2mjCea2H1mEriAJPqMPMKIinh1lQJEZufnfCcPxbZLKTtl6zHtHgOztejd1gV/nUyCVKD4MCMfBDy9C/Af8pWx6akOg/QSQNIGA2AI6zprHn9zEjpFIzXJYvruVI22Yt6oF9Xnt7Ki82wRK2M96r4kj6cwSs4exMPGv8fWMrFTm0Br6p6T+HZsxyyn2ChuPIgpfisnce/ZaU/0xCZhK/K79+TK2GeeChq5oEpua/1tx4+kDHi7H9381pLJmy2oXW060c2mmwA9+EpcuwEDhr8fsnghbv41u7b1NhEmWNVUy29Dwaz61PPGUdh5DsvaKLWC+raZ/6UEKPw+tiABJ5o6u2jAWgmEYmmJCKapNgtfPc6D+O0aHH9oqh6u4+8NRAhusPZzDGWBr6AT4pexgWFeEhZhn6bXM9HhpUe0IhOTw5D+tqXrTlNon4kjYibiMUFy1h2YyYS3IEdu1J4xqvo0rFyCxF1C+P6ubc0tClRPkXg=="; - - string actualPlain = Utilities.AESUtility.DecryptWithGCM(key: key, iv: iv, aad: aad, cipherText: cipherText); + + string actualPlain = Utilities.AESUtility.DecryptWithGCM(key: key, nonce: nonce, aad: aad, cipherText: cipherText); string expectedPlain = "-----BEGIN CERTIFICATE-----\nMIID3DCCAsSgAwIBAgIUGZNrTcamx3sFSJsQli3v9C6gZe8wDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\nFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\nQ0EwHhcNMjAwODAxMDczNTE4WhcNMjUwNzMxMDczNTE4WjBuMRgwFgYDVQQDDA9U\nZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl\nbnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo\nZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOAYHqxCqaRzoTIvgV\nixaYJJvmvHbiczbx5MQ9XL1ITSFxkTsNsk7RKHnO7eBS5imheJgQwd22Ky+XclSe\n7B4odssu/l/+gHo2gooTYrrCpQrOkpvGMf8R8aI56BQIF+vsomDvVq1NojHV2Fql\njMwFXzhj2EmU6p6gDv9iL7q1NrfnxFx8iJe4OhIB5Ek4qn1xXxrTUhiULd2vXlbI\nXhRetZSNcJsLt5Rw7D7c8F+aX2JchfeqsZECwKW7bSjMbVWWC6M9MgkB/aId8P0y\n7qEiiXFJkfkg1I/E1ud2apopsid5tdCyRRR6+MhhX2EC8S04MN4soUT7haqNNxX2\nrKHnAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DBlBgNVHR8EXjBc\nMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5jb20uY24vcHVibGljL2l0cnVzY3Js\nP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJ\nKoZIhvcNAQELBQADggEBAJyg2z4oLQmPfftLQWyzbUc9ONhRMtfA+tVlVBgtLLKn\nWuDlsmEntheM07fu84F4pcfs3yHzjD7pAOFbO4Yt1yhQ50DK35sjbRWepPdWJZLl\nni7KBctcmm0o4zq37oB7vonmBEbFqYs9DaINYOjgI3J25iSBkPVC7dBbvFj2xB0L\ncIcXipq30tDdC/oUem27MNzwZAt49WthKhw6u3HSkcE5cO4LyYTsJhSyG/7LXwvV\nMgX4Jyzo0SSiGOU1/beaZssTVI8sTPJVlHWjhNE3Lc2SaAlKGfGwvt0X3cEZEF+7\noEZIFTkkAF2JhqfnpR3gST0G8Umq1SaVtCPP/zVI8x0=\n-----END CERTIFICATE-----"; Assert.Equal(expectedPlain, actualPlain); diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM4UtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM4UtilityTests.cs new file mode 100644 index 00000000..ad0148fe --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM4UtilityTests.cs @@ -0,0 +1,22 @@ +using System.Text; +using Xunit; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests +{ + public class TestCase_SM4UtilityTests + { + [Fact(DisplayName = "测试用例:SM4-GCM 解密")] + public void TestSM4GCMDecrypt() + { + string key = "f09b03a7a1902b5b"; + string nonce = "aae8c2e79c5b"; + string aad = "certificate"; + string cipherText = "fB6kij5HTmN4mXBIu6MaZIjDp8jRt2iziXIGF34yLSHo0Gkt6Y6CgjdWZvCxht2UfC52VbHfeNMS4kOBRtpZ/LO9pIoA/V7Qs/V5RD3iqxYBgbCIdgGNmjJN9mcT7VZbBYLnKAp8PQsbVbmHxmmXTpY/xj+geUuF3ELIhTjLcjOB4UW0/FNUjtM9taYriGwVUKo9cJ+652QkFs3QBerxMzqPEObG3NS2h99WrtV1LmayBN2m9ncJJ8Kjk2oSdn/P+ua2CpqM4G6f8AFeitfdRdS94js3A/6GEZGY8WFHMrslvfc8YMj72MxPJ1qv3zF15BBzEKrRcoZWdywRyXDGPpTIXrlGFOiGzxS07FdekYj5V6qW7pD0wuqVug3fyU5wt+Jx4Yvk8dG2voywkqxFzRQfbTL/Wv0+54a+AFK2HFZjY9oXXgiVHpqPK0fv+meTSMoVyQTbVXwfZvNGm5sROJlfM5tiV6UKRbBUx+/6+39H2unwnItIFIMANKJPiS9x8B3Vkxuc8ZdpZe8owfrL6otEPzWSXsDK0zKYvvK+Mxmb40cZZKiaLzXmsEiVTQ9JJX9ycE6xFiCQTjd91zy5Os7xaY6vLPLegdWCbbOq4bpIpX0vt0RPHLWvG3vyzX6tPiy77z9AxOohd4hqLpiH1nA2Ua3+AjcYRYjU7NA1PnbFThBulH7O1TMFtShpLdlZAGl4op926iZVRYZjGAdfbkLCrHmoggsB15DjFWbNwEuuZzpuX5tcIZLXqxNdOrECrwXAYM6K1HtBC/wDpqlNfFb7LEaKRhv++KHE3K6UXekLpPR5h22ttIMbMAjZrBdjXVA9fagPtB1gmY0fH4j0b8Wd6j6Fj8MjjhirvTmRAZHV0Zq1dqw8QDFELOVE1bczZJ2vUDgGoL5cCNgAsjOCejJCsSe2MWiWQgvM8zmwv2eSKHSsVuQ6E4urovXmVdoEdDdar10juldAVVQ5FfSbtZrNpVmK2EHx2s7rBKySR+KWpf1DXNQWa9YmSzxQH4RrVaUB5OSwqhDyxOH4i9HeNzHsK56y/6tumToKA/QCUp2CqZp++OrLNkgy7n3bx1NN3Qm8uIIWQVT5HKJoI0CmvzuSrHAxQQVTkcZ5dMsUF/RQy0lN8ztuF0yTEqRWl4efUoX8RViGJkTyGkQngjZX4XnH7S4mAuvHFBTGFzFmSGUfxeWO9lIXhPUN1yslWxQBxCmGIp7YGmB9k+8WF8z1OgRaYbCbql7DveTcegNyoZXzfpjF3YJ4AQXuvSlKIwK7qsLEKBwmawxwt2yT0SjWuO41AgpeLH9s2JRLPZ+ZY+1RWPG8a4lfl+fMchIQH8qmULGgaqlzbq7VZmRnmeyYOz75v1mvSqNFijCrnlzB4QZcSNI05ylTtvRE6C/WezeY3dc9uVdBRWp1i8jzm6RhFid40C9SCtxrvbv/O3RjfiwsibZp4JWNbIPBmt08aUn0nMjpoaXcQXPTIDLtiYCziECD0Mlf5mz2DaK0ecltI5CfsFT/Yi0zmrbmOARiOP3zkYiCP8bVcWu2sOyC1HB5Dlf3ak8ver5lOJIq43dIG0++LwwxGzgIiQx5MPdWCs1dKD6cREKNdtyjcW689N6Q0hwC7Lj4od2W6oYe2cOOfp6IriKlYUP//a1lAnBI6xaHcy50YoJud77Et3nCUWWyZLS75fMaqNqG+cioe1arnk/n9vIpibZqOyKbPJvNcbTAimshZZY8BJgeRTbm/Jjaka79WFXKxq9gwnl+jUjuxpEhMlkoRAezH4EGK/hdGjnfSk5L54ppxxEGMLBlpjbEaUA+9mSj5OcSqz1iSHUvI8v9RHVyXFixRXCvv9EHsJew"; + + string actualPlain = Utilities.SM4Utility.DecryptWithGCM(key: key, nonce: nonce, aad: aad, cipherText: cipherText); + string expectedPlain = "-----BEGIN CERTIFICATE-----MIID3DCCAsSgAwIBAgIUGZNrTcamx3sFSJsQli3v9C6gZe8wDQYJKoZIhvcNAQELBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsTFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3QgQ0EwHhcNMjAwODAxMDczNTE4WhcNMjUwNzMxMDczNTE4WjBuMRgwFgYDVQQDDA9UZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRlbnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpoZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOAYHqxCqaRzoTIvgVixaYJJvmvHbiczbx5MQ9XL1ITSFxkTsNsk7RKHnO7eBS5imheJgQwd22Ky+XclSe7B4odssu/l/+gHo2gooTYrrCpQrOkpvGMf8R8aI56BQIF+vsomDvVq1NojHV2FqljMwFXzhj2EmU6p6gDv9iL7q1NrfnxFx8iJe4OhIB5Ek4qn1xXxrTUhiULd2vXlbIXhRetZSNcJsLt5Rw7D7c8F+aX2JchfeqsZECwKW7bSjMbVWWC6M9MgkB/aId8P0y7qEiiXFJkfkg1I/E1ud2apopsid5tdCyRRR6+MhhX2EC8S04MN4soUT7haqNNxX2rKHnAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DBlBgNVHR8EXjBcMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5jb20uY24vcHVibGljL2l0cnVzY3JsP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJKoZIhvcNAQELBQADggEBAJyg2z4oLQmPfftLQWyzbUc9ONhRMtfA+tVlVBgtLLKnWuDlsmEntheM07fu84F4pcfs3yHzjD7pAOFbO4Yt1yhQ50DK35sjbRWepPdWJZLlni7KBctcmm0o4zq37oB7vonmBEbFqYs9DaINYOjgI3J25iSBkPVC7dBbvFj2xB0LcIcXipq30tDdC/oUem27MNzwZAt49WthKhw6u3HSkcE5cO4LyYTsJhSyG/7LXwvVMgX4Jyzo0SSiGOU1/beaZssTVI8sTPJVlHWjhNE3Lc2SaAlKGfGwvt0X3cEZEF+7oEZIFTkkAF2JhqfnpR3gST0G8Umq1SaVtCPP/zVI8x0=-----END CERTIFICATE-----"; + + Assert.Equal(expectedPlain, actualPlain); + } + } +}