From d3513dbc465033a8057bd2f98c8edf340b2d1dc9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 19 Oct 2021 15:09:05 +0800 Subject: [PATCH] =?UTF-8?q?test(wxapi):=20=E5=A2=9E=E5=8A=A0=20AES-CBC=20?= =?UTF-8?q?=E8=A7=A3=E5=AF=86=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Utilities/AESUtility.cs | 45 +++++++++++++++++++ .../Utilities/Internal/XmlUtility.cs | 9 ++-- .../Utilities/Internal/XmlUtility.cs | 9 ++-- .../Utilities/Internal/XmlUtility.cs | 9 ++-- .../WechatApiSecurityTests.cs | 26 +++++++---- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs index 07d0a63f..1d79405d 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs @@ -35,6 +35,31 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities } } + /// + /// 基于 CBC 模式加密数据。 + /// + /// AES 密钥字节数组。 + /// 加密使用的初始化向量字节数组。 + /// 待加密数据字节数组。 + /// 加密后的数据字节数组。 + public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes) + { + if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); + if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes)); + if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + + using (SymmetricAlgorithm aes = Aes.Create()) + { + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.Key = keyBytes; + aes.IV = ivBytes; + + using ICryptoTransform transform = aes.CreateEncryptor(); + return transform.TransformFinalBlock(plainBytes, 0, plainBytes.Length); + } + } + /// /// 基于 CBC 模式解密数据。 /// @@ -54,5 +79,25 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities ); return Encoding.UTF8.GetString(plainBytes); } + + /// + /// 基于 CBC 模式加密数据。 + /// + /// 经 Base64 编码后的 AES 密钥。 + /// 经 Base64 编码后的 AES 初始化向量。 + /// 待加密文本。 + /// 经 Base64 编码的加密后的数据。 + public static string EncryptWithCBC(string encodingKey, string encodingIV, string plainText) + { + if (encodingKey == null) throw new ArgumentNullException(nameof(encodingKey)); + if (plainText == null) throw new ArgumentNullException(nameof(plainText)); + + byte[] plainBytes = EncryptWithCBC( + keyBytes: Convert.FromBase64String(encodingKey), + ivBytes: Convert.FromBase64String(encodingIV), + plainBytes: Encoding.UTF8.GetBytes(plainText) + ); + return Convert.ToBase64String(plainBytes); + } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/Internal/XmlUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/Internal/XmlUtility.cs index 8cd97d73..190a3260 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/Internal/XmlUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/Internal/XmlUtility.cs @@ -11,18 +11,19 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities internal static class XmlUtility { // REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies - private static Hashtable _serializers = new Hashtable(); + private static readonly Hashtable _xmlSerializers = new Hashtable(); + private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml"); private static XmlSerializer GetTypedSerializer(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString(); - XmlSerializer? xmlSerializer = (XmlSerializer?)_serializers[skey]; + XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey]; if (xmlSerializer == null) { - xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml")); - _serializers[skey] = xmlSerializer; + xmlSerializer = new XmlSerializer(type, _xmlRoot); + _xmlSerializers[skey] = xmlSerializer; } return xmlSerializer; diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Utilities/Internal/XmlUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Utilities/Internal/XmlUtility.cs index 4d305cc9..569681fc 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Utilities/Internal/XmlUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Utilities/Internal/XmlUtility.cs @@ -11,18 +11,19 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities internal static class XmlUtility { // REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies - private static Hashtable _serializers = new Hashtable(); + private static readonly Hashtable _xmlSerializers = new Hashtable(); + private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml"); private static XmlSerializer GetTypedSerializer(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString(); - XmlSerializer? xmlSerializer = (XmlSerializer?)_serializers[skey]; + XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey]; if (xmlSerializer == null) { - xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml")); - _serializers[skey] = xmlSerializer; + xmlSerializer = new XmlSerializer(type, _xmlRoot); + _xmlSerializers[skey] = xmlSerializer; } return xmlSerializer; diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/Internal/XmlUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/Internal/XmlUtility.cs index 0b331691..19d8e8d7 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/Internal/XmlUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/Internal/XmlUtility.cs @@ -11,18 +11,19 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities internal static class XmlUtility { // REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies - private static Hashtable _serializers = new Hashtable(); + private static readonly Hashtable _xmlSerializers = new Hashtable(); + private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml"); private static XmlSerializer GetTypedSerializer(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString(); - XmlSerializer? xmlSerializer = (XmlSerializer?)_serializers[skey]; + XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey]; if (xmlSerializer == null) { - xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml")); - _serializers[skey] = xmlSerializer; + xmlSerializer = new XmlSerializer(type, _xmlRoot); + _xmlSerializers[skey] = xmlSerializer; } return xmlSerializer; diff --git a/test/SKIT.FlurlHttpClient.Wechat.Api.UnitTests/WechatApiSecurityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.Api.UnitTests/WechatApiSecurityTests.cs index fa91c2ba..439a77e5 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.Api.UnitTests/WechatApiSecurityTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.Api.UnitTests/WechatApiSecurityTests.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests @@ -16,9 +11,9 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests string rawData = "jsapi_ticket=HoagFKDcsGMVCIY2vOjf9nGrZ3eaM0qXs5ROFN_3k_HrGc0VocemA6wMXkvrL-Ei4IitXxwKF62CJWR8mWXZ3Q&noncestr=e7b435f73835402da44f16640ddc8696×tamp=1621348162&url=https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign"; string actualHash = Security.SHA1Utility.Hash(rawData); - string expectdHash = "b214ea1f8ae019c207f8c6ffb843c8474cbab28c"; + string expectedHash = "b214ea1f8ae019c207f8c6ffb843c8474cbab28c"; - Assert.Equal(expectdHash, actualHash, ignoreCase: true); + Assert.Equal(expectedHash, actualHash, ignoreCase: true); } [Fact(DisplayName = "信息摘要(HMAC-SHA-256)")] @@ -28,9 +23,22 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests string rawData = "appid=wx1234567&offer_id=12345678&openid=odkx20ENSNa2w5y3g_qOkOvBNM1g&pf=android&ts=1507530737&zone_id=1&org_loc=/cgi-bin/midas/getbalance&method=POST&secret=zNLgAGgqsEWJOg1nFVaO5r7fAlIQxr1u"; string actualHash = Security.HMACSHA256Utility.Hash(secret, rawData); - string expectdHash = "1ad64e8dcb2ec1dc486b7fdf01f4a15159fc623dc3422470e51cf6870734726b"; + string expectedHash = "1ad64e8dcb2ec1dc486b7fdf01f4a15159fc623dc3422470e51cf6870734726b"; - Assert.Equal(expectdHash, actualHash, ignoreCase: true); + Assert.Equal(expectedHash, actualHash, ignoreCase: true); + } + + [Fact(DisplayName = "AES-CBC 解密")] + public void AesCbcDecryptTest() + { + string iv = "KEWv/gyiIwAfHvjrLeaX6w=="; + string key = "YZJqKnNFi0KAiKUc0ggC2g=="; + string cipherText = "Gu2PVnxVWl+jK4F8c0liGxfkB5Bj3m5HRvwgEIk1Yb+36RZ3Bg7YmUnud/ooiHz0PQroipsH7GCjlGwUeT04NwmrFaP1y3dRYPLpS43ed9QZWcFIFo+8vTs3Zco6S98DUvaNEAs8duhz/BzfBOZaIHMziRqEtPFI0ZDzCgJluBirJ6Wl3UkygZ5/QLo3KA53qGdip7K48Rq8XbCwuidTCw=="; + + string expectedPlainData = "{\"phoneNumber\":\"186****5613\",\"purePhoneNumber\":\"186****5613\",\"countryCode\":\"86\",\"watermark\":{\"timestamp\":1634545675,\"appid\":\"wxc****17e87e0e0a7\"}}"; + string actualPlainData = Utilities.AESUtility.DecryptWithCBC(encodingKey: key, encodingIV: iv, encodingCipherText: cipherText); + + Assert.Equal(expectedPlainData, actualPlainData, ignoreCase: true); } } }