diff --git a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs index 6e680ecb..941d92c1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs @@ -88,31 +88,38 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes)); #if NET5_0_OR_GREATER - using (AesGcm aes = new AesGcm(keyBytes)) + if (AesGcm.IsSupported) { - byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE]; - byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; - Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length); - Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length); + if (!string.Equals(paddingMode, PADDING_MODE_NOPADDING, StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(); - byte[] plainBytes = new byte[cipherWithoutTagBytes.Length]; - aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes); + using (AesGcm aes = new AesGcm(keyBytes)) + { + byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE]; + byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; + Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length); + Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length); + + byte[] plainBytes = new byte[cipherWithoutTagBytes.Length]; + aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes); + return plainBytes; + } + } +#endif + { + IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); + ICipherParameters cipherParams = new AeadParameters( + new KeyParameter(keyBytes), + TAG_LENGTH_BYTE * 8, + nonceBytes, + associatedDataBytes + ); + 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; } -#else - IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); - ICipherParameters cipherParams = new AeadParameters( - new KeyParameter(keyBytes), - TAG_LENGTH_BYTE * 8, - nonceBytes, - associatedDataBytes - ); - 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; -#endif } /// @@ -204,32 +211,38 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes)); #if NET5_0_OR_GREATER - using (AesGcm aes = new AesGcm(keyBytes)) + if (AesGcm.IsSupported) { - byte[] cipherBytes = new byte[plainBytes.Length]; - byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; - aes.Encrypt(nonceBytes, plainBytes, cipherBytes, tagBytes, associatedDataBytes); + if (!string.Equals(paddingMode, PADDING_MODE_NOPADDING, StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(); - byte[] cipherWithTagBytes = new byte[cipherBytes.Length + tagBytes.Length]; - Buffer.BlockCopy(cipherBytes, 0, cipherWithTagBytes, 0, cipherBytes.Length); - Buffer.BlockCopy(tagBytes, 0, cipherWithTagBytes, cipherBytes.Length, tagBytes.Length); - return cipherWithTagBytes; + using (AesGcm aes = new AesGcm(keyBytes)) + { + byte[] cipherBytes = new byte[plainBytes.Length]; + byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; + aes.Encrypt(nonceBytes, plainBytes, cipherBytes, tagBytes, associatedDataBytes); + + byte[] cipherWithTagBytes = new byte[cipherBytes.Length + tagBytes.Length]; + Buffer.BlockCopy(cipherBytes, 0, cipherWithTagBytes, 0, cipherBytes.Length); + Buffer.BlockCopy(tagBytes, 0, cipherWithTagBytes, cipherBytes.Length, tagBytes.Length); + return cipherWithTagBytes; + } } -#else - - IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); - ICipherParameters cipherParams = new AeadParameters( - new KeyParameter(keyBytes), - TAG_LENGTH_BYTE * 8, - nonceBytes, - associatedDataBytes - ); - cipher.Init(true, cipherParams); - byte[] cipherBytes = new byte[cipher.GetOutputSize(plainBytes.Length)]; - int len = cipher.ProcessBytes(plainBytes, 0, plainBytes.Length, cipherBytes, 0); - cipher.DoFinal(cipherBytes, len); - return cipherBytes; #endif + { + IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); + ICipherParameters cipherParams = new AeadParameters( + new KeyParameter(keyBytes), + TAG_LENGTH_BYTE * 8, + nonceBytes, + associatedDataBytes + ); + cipher.Init(true, cipherParams); + byte[] cipherBytes = new byte[cipher.GetOutputSize(plainBytes.Length)]; + int len = cipher.ProcessBytes(plainBytes, 0, plainBytes.Length, cipherBytes, 0); + cipher.DoFinal(cipherBytes, len); + return cipherBytes; + } } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs index 8a0406e8..0ce71bb0 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs @@ -23,7 +23,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem) { - if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; + const string PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + + if (!privateKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(privateKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -56,15 +59,18 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities } privateKeyPem = privateKeyPem - .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) - .Replace("-----END PRIVATE KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(privateKeyPem); } private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem) { - if (!publicKeyPem.StartsWith("-----BEGIN PUBLIC KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PUBLIC KEY-----"; + const string PKCS8_FOOTER = "-----END PUBLIC KEY-----"; + + if (!publicKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(publicKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -91,8 +97,8 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities } publicKeyPem = publicKeyPem - .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) - .Replace("-----END PUBLIC KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(publicKeyPem); } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Extensions/__Internal/WechatTenpayBusinessClientSigningExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Extensions/__Internal/WechatTenpayBusinessClientSigningExtensions.cs index 0791ce72..18b1a50c 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Extensions/__Internal/WechatTenpayBusinessClientSigningExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Extensions/__Internal/WechatTenpayBusinessClientSigningExtensions.cs @@ -63,7 +63,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness break; } - bool valid = Utilities.RSAUtility.Verify( + bool valid = Utilities.RSAUtility.VerifyWithSHA256( publicKeyPem: client.Credentials.TBEPCertificatePublicKey, messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64) diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Interceptors/WechatTenpayBusinessRequestSigningInterceptor.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Interceptors/WechatTenpayBusinessRequestSigningInterceptor.cs index ae8369a4..2cb88143 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Interceptors/WechatTenpayBusinessRequestSigningInterceptor.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Interceptors/WechatTenpayBusinessRequestSigningInterceptor.cs @@ -75,13 +75,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors { try { - sign = Utilities.RSAUtility.Sign(_platformCertPk, signData).Value!; + sign = Utilities.RSAUtility.SignWithSHA256(_platformCertPk, signData).Value!; if (softSignRequired) { byte[] keyBytes = EncodedString.FromBase64String(_enterpriseCertPk!); byte[] msgBytes = EncodedString.FromBase64String(sign); - softSign = EncodedString.ToBase64String(Utilities.RSAUtility.Sign(keyBytes, msgBytes)).Value!; + softSign = EncodedString.ToBase64String(Utilities.RSAUtility.SignWithSHA256(keyBytes, msgBytes)).Value!; } } catch (Exception ex) diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs index efd8eb4f..8e072ace 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using System.Text.RegularExpressions; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; @@ -23,11 +24,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities /// /// 签名算法:SHA-256withRSA。 /// - public const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA"; + private const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA"; private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem) { - if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; + const string PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + + if (!privateKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(privateKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -60,15 +64,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities } privateKeyPem = privateKeyPem - .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) - .Replace("-----END PRIVATE KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(privateKeyPem); } private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem) { - if (!publicKeyPem.StartsWith("-----BEGIN PUBLIC KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PUBLIC KEY-----"; + const string PKCS8_FOOTER = "-----END PUBLIC KEY-----"; + + if (!publicKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(publicKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -95,8 +102,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities } publicKeyPem = publicKeyPem - .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) - .Replace("-----END PUBLIC KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(publicKeyPem); } @@ -111,17 +118,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); } - private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] messageBytes, string digestAlgorithm) + private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] messageBytes, string algorithm) { - ISigner signer = SignerUtilities.GetSigner(digestAlgorithm); + ISigner signer = SignerUtilities.GetSigner(algorithm); signer.Init(true, rsaPrivateKeyParams); signer.BlockUpdate(messageBytes, 0, messageBytes.Length); return signer.GenerateSignature(); } - private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] messageBytes, byte[] signBytes, string digestAlgorithm) + private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] messageBytes, byte[] signBytes, string algorithm) { - ISigner signer = SignerUtilities.GetSigner(digestAlgorithm); + ISigner signer = SignerUtilities.GetSigner(algorithm); signer.Init(false, rsaPublicKeyParams); signer.BlockUpdate(messageBytes, 0, messageBytes.Length); return signer.VerifySignature(signBytes); @@ -142,66 +149,78 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities } /// - /// 使用私钥生成签名。 + /// 使用私钥基于 SHA-256 算法生成签名。 /// - /// PKCS#1/PKCS#8 私钥字节数组。 + /// PKCS#8 私钥字节数组。 /// 待签名的数据字节数组。 - /// 签名算法。(默认值:) /// 签名字节数组。 - public static byte[] Sign(byte[] privateKeyBytes, byte[] messageBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] messageBytes) { if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes)); +#if NET5_0_OR_GREATER + using (RSA rsa = RSA.Create()) + { + rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); + return rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } +#else RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes); - return Sign(rsaPrivateKeyParams, messageBytes, digestAlgorithm); + return Sign(rsaPrivateKeyParams, messageBytes, DIGEST_ALGORITHM_SHA256); +#endif } /// - /// 使用私钥生成签名。 + /// 使用私钥基于 SHA-256 算法生成签名。 /// /// PKCS#1/PKCS#8 私钥(PEM 格式)。 /// 待签名的数据。 - /// 签名算法。(默认值:) /// 经过 Base64 编码的签名。 - public static EncodedString Sign(string privateKeyPem, string messageData, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static EncodedString SignWithSHA256(string privateKeyPem, string messageData) { if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem)); if (messageData is null) throw new ArgumentNullException(nameof(messageData)); byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem); byte[] messageBytes = EncodedString.FromLiteralString(messageData); - byte[] signBytes = Sign(privateKeyBytes, messageBytes, digestAlgorithm); + byte[] signBytes = SignWithSHA256(privateKeyBytes, messageBytes); return EncodedString.ToBase64String(signBytes); } /// - /// 使用公钥验证签名。 + /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#1/PKCS#8 公钥字节数组。 /// 待验证的数据字节数组。 /// 签名字节数组。 - /// 签名算法。(默认值:) /// 验证结果。 - public static bool Verify(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes) { if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes)); if (signBytes is null) throw new ArgumentNullException(nameof(signBytes)); +#if NET5_0_OR_GREATER + using (RSA rsa = RSA.Create()) + { + rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _); + return rsa.VerifyData(messageBytes, signBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } +#else RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes); - return Verify(rsaPublicKeyParams, messageBytes, signBytes, digestAlgorithm); + return Verify(rsaPublicKeyParams, messageBytes, signBytes, DIGEST_ALGORITHM_SHA256); +#endif } /// - /// 使用公钥验证签名。 + /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#1/PKCS#8 公钥(PEM 格式)。 /// 待验证的数据。 /// 经过编码后的(通常为 Base64)签名。 - /// 签名算法。(默认值:) /// 验证结果。 - public static bool Verify(string publicKeyPem, string messageData, EncodedString encodingSignature, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static bool VerifyWithSHA256(string publicKeyPem, string messageData, EncodedString encodingSignature) { if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem)); if (messageData is null) throw new ArgumentNullException(nameof(messageData)); @@ -210,13 +229,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem); byte[] messageBytes = EncodedString.FromLiteralString(messageData); byte[] signBytes = EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64); - return Verify(publicKeyBytes, messageBytes, signBytes, digestAlgorithm); + return VerifyWithSHA256(publicKeyBytes, messageBytes, signBytes); } /// /// 使用私钥基于 ECB 模式解密数据。 /// - /// PKCS#1/PKCS#8 私钥字节数组。 + /// PKCS#8 私钥字节数组。 /// 待解密的数据字节数组。 /// 填充模式。(默认值:) /// 解密后的数据字节数组。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/Extensions/WechatTenpayGlobalClientResponseDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/Extensions/WechatTenpayGlobalClientResponseDecryptionExtensions.cs index a491c25c..a0da0b76 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/Extensions/WechatTenpayGlobalClientResponseDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/Extensions/WechatTenpayGlobalClientResponseDecryptionExtensions.cs @@ -1,5 +1,12 @@ +using System; + namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global { + using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global.Models; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities; + public static class WechatTenpayGlobalClientResponseDecryptionExtensions { /// @@ -8,9 +15,43 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global /// /// /// - public static Models.QueryCertificatesResponse DecryptResponseSensitiveProperty(this WechatTenpayGlobalClient client, Models.QueryCertificatesResponse response) + public static QueryCertificatesResponse DecryptResponseSensitiveProperty(this WechatTenpayGlobalClient client, QueryCertificatesResponse response) { - return WechatTenpayClientResponseDecryptionExtensions.DecryptResponseSensitiveProperty(client, response); + if (client is null) throw new ArgumentNullException(nameof(client)); + if (response is null) throw new ArgumentNullException(nameof(response)); + + if (response.CertificateList is null) + return response; + + foreach (var certificate in response.CertificateList) + { + if (certificate.EncryptCertificate is null) + continue; + + switch (certificate.EncryptCertificate.Algorithm) + { + case EncryptionAlgorithms.AEAD_AES_256_GCM: + { + if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret)) + throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set."); + + certificate.EncryptCertificate.CipherText = AESUtility.DecryptWithGCM( + encodingKey: new EncodedString(client.Credentials.MerchantV3Secret, EncodingKinds.Literal), + encodingNonce: new EncodedString(certificate.EncryptCertificate.Nonce, EncodingKinds.Literal), + encodingAssociatedData: new EncodedString(certificate.EncryptCertificate.AssociatedData, EncodingKinds.Literal), + encodingCipher: new EncodedString(certificate.EncryptCertificate.CipherText, EncodingKinds.Base64) + )!; + } + break; + + default: + { + throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{certificate.EncryptCertificate.Algorithm}\"."); + } + } + } + + return response; } /// @@ -22,6 +63,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global public static TResponse DecryptResponseSensitiveProperty(this WechatTenpayGlobalClient client, TResponse response) where TResponse : WechatTenpayGlobalResponse { + // [GET] /certificates 接口的响应模型需特殊处理 + if (response is QueryCertificatesResponse queryCertificatesResponse) + { + return (DecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!; + } + return WechatTenpayClientResponseDecryptionExtensions.DecryptResponseSensitiveProperty(client, response); } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs index 36df8e22..ab7e95b1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs @@ -78,10 +78,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global /// /// /// - public new Task SendFlurlRequestAsync(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default) + public async new Task SendFlurlRequestAsync(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default) where T : WechatTenpayGlobalResponse, new() { - return base.SendFlurlRequestAsync(flurlRequest, httpContent, cancellationToken); + if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest)); + + using IFlurlResponse flurlResponse = await base.SendFlurlRequestAsync(flurlRequest, httpContent, cancellationToken).ConfigureAwait(false); + return await WrapFlurlResponseAsJsonAsync(flurlResponse, cancellationToken).ConfigureAwait(false); } /// @@ -92,10 +95,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global /// /// /// - public new Task SendFlurlRequestAsJsonAsync(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default) + public async new Task SendFlurlRequestAsJsonAsync(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default) where T : WechatTenpayGlobalResponse, new() { - return base.SendFlurlRequestAsJsonAsync(flurlRequest, data, cancellationToken); + if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest)); + + bool isSimpleRequest = data is null || + flurlRequest.Verb == HttpMethod.Get || + flurlRequest.Verb == HttpMethod.Head || + flurlRequest.Verb == HttpMethod.Options; + using IFlurlResponse flurlResponse = isSimpleRequest ? + await base.SendFlurlRequestAsync(flurlRequest, null, cancellationToken).ConfigureAwait(false) : + await base.SendFlurlRequestAsJsonAsync(flurlRequest, data, cancellationToken).ConfigureAwait(false); + return await WrapFlurlResponseAsJsonAsync(flurlResponse, cancellationToken).ConfigureAwait(false); + } + + private new async Task WrapFlurlResponseAsJsonAsync(IFlurlResponse flurlResponse, CancellationToken cancellationToken = default) + where TResponse : WechatTenpayGlobalResponse, new() + { + TResponse result = await base.WrapFlurlResponseAsJsonAsync(flurlResponse, cancellationToken).ConfigureAwait(false); + + if (AutoDecryptResponseSensitiveProperty && result.IsSuccessful()) + { + this.DecryptResponseSensitiveProperty(result); + } + + return result; } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs index f6cceaf7..e49dfc07 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs @@ -38,7 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString(); string nonce = Guid.NewGuid().ToString("N"); string package = $"prepay_id={prepayId}"; - string sign = Utilities.RSAUtility.Sign( + string sign = Utilities.RSAUtility.SignWithSHA256( privateKeyPem: client.Credentials.MerchantCertificatePrivateKey, messageData: $"{appId}\n{timestamp}\n{nonce}\n{package}\n" )!; @@ -96,7 +96,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString(); string nonce = Guid.NewGuid().ToString("N"); - string sign = Utilities.RSAUtility.Sign( + string sign = Utilities.RSAUtility.SignWithSHA256( privateKeyPem: client.Credentials.MerchantCertificatePrivateKey, messageData: $"{appId}\n{timestamp}\n{nonce}\n{prepayId}\n" )!; diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs index 5062bd4b..3b0d09d5 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs @@ -67,7 +67,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { try { - bool valid = Utilities.RSAUtility.VerifyByCertificate( + bool valid = Utilities.RSAUtility.VerifyWithSHA256ByCertificate( certificatePem: certificate, messageData: message, encodingSignature: new EncodedString(signature, EncodingKinds.Base64) diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs index 98ec7522..81340a76 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs @@ -62,7 +62,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors { try { - sign = Utilities.RSAUtility.Sign(_mchCertPk, signData).Value!; + sign = Utilities.RSAUtility.SignWithSHA256(_mchCertPk, signData).Value!; } catch (Exception ex) { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs index 7e77bb9e..f5f5d443 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/AESUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Cryptography; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; @@ -39,18 +40,39 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (cipherBytes is 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($"AES/GCM/{paddingMode}"); - ICipherParameters cipherParams = new AeadParameters( - new KeyParameter(keyBytes), - TAG_LENGTH_BYTE * 8, - nonceBytes, - associatedDataBytes - ); - 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; +#if NET5_0_OR_GREATER + if (AesGcm.IsSupported) + { + if (!string.Equals(paddingMode, PADDING_MODE_NOPADDING, StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(); + + using (AesGcm aes = new AesGcm(keyBytes)) + { + byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE]; + byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; + Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length); + Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length); + + byte[] plainBytes = new byte[cipherWithoutTagBytes.Length]; + aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes); + return plainBytes; + } + } +#endif + { + IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); + ICipherParameters cipherParams = new AeadParameters( + new KeyParameter(keyBytes), + TAG_LENGTH_BYTE * 8, + nonceBytes, + associatedDataBytes + ); + 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; + } } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs index a4c0b086..54063933 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using System.Text.RegularExpressions; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; @@ -29,11 +30,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// /// 签名算法:SHA-256withRSA。 /// - public const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA"; + private const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA"; private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem) { - if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; + const string PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + + if (!privateKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(privateKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -66,15 +70,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities } privateKeyPem = privateKeyPem - .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) - .Replace("-----END PRIVATE KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(privateKeyPem); } private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem) { - if (!publicKeyPem.StartsWith("-----BEGIN PUBLIC KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PUBLIC KEY-----"; + const string PKCS8_FOOTER = "-----END PUBLIC KEY-----"; + + if (!publicKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(publicKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -101,12 +108,24 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities } publicKeyPem = publicKeyPem - .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) - .Replace("-----END PUBLIC KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(publicKeyPem); } + private static byte[] ConvertCertificatePemToByteArray(string certificatePem) + { + const string CER_HEADER = "-----BEGIN CERTIFICATE-----"; + const string CER_FOOTER = "-----END CERTIFICATE-----"; + + certificatePem = certificatePem + .Replace(CER_HEADER, string.Empty) + .Replace(CER_FOOTER, string.Empty); + certificatePem = Regex.Replace(certificatePem, "\\s+", string.Empty); + return Convert.FromBase64String(certificatePem); + } + private static X509Certificate ParseCertificatePemToX509(string certificatePem) { using (TextReader sreader = new StringReader(certificatePem)) @@ -126,17 +145,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); } - private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] messageBytes, string digestAlgorithm) + private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] messageBytes, string algorithm) { - ISigner signer = SignerUtilities.GetSigner(digestAlgorithm); + ISigner signer = SignerUtilities.GetSigner(algorithm); signer.Init(true, rsaPrivateKeyParams); signer.BlockUpdate(messageBytes, 0, messageBytes.Length); return signer.GenerateSignature(); } - private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] messageBytes, byte[] signBytes, string digestAlgorithm) + private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] messageBytes, byte[] signBytes, string algorithm) { - ISigner signer = SignerUtilities.GetSigner(digestAlgorithm); + ISigner signer = SignerUtilities.GetSigner(algorithm); signer.Init(false, rsaPublicKeyParams); signer.BlockUpdate(messageBytes, 0, messageBytes.Length); return signer.VerifySignature(signBytes); @@ -157,66 +176,78 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities } /// - /// 使用私钥生成签名。 + /// 使用私钥基于 SHA-256 算法生成签名。 /// - /// PKCS#1/PKCS#8 私钥字节数组。 + /// PKCS#8 私钥字节数组。 /// 待签名的数据字节数组。 - /// 签名算法。(默认值:) /// 签名字节数组。 - public static byte[] Sign(byte[] privateKeyBytes, byte[] messageBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] messageBytes) { if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes)); +#if NET5_0_OR_GREATER + using (RSA rsa = RSA.Create()) + { + rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); + return rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } +#else RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes); - return Sign(rsaPrivateKeyParams, messageBytes, digestAlgorithm); + return Sign(rsaPrivateKeyParams, messageBytes, DIGEST_ALGORITHM_SHA256); +#endif } /// - /// 使用私钥生成签名。 + /// 使用私钥基于 SHA-256 算法生成签名。 /// /// PKCS#1/PKCS#8 私钥(PEM 格式)。 /// 待签名的数据。 - /// 签名算法。(默认值:) /// 经过 Base64 编码的签名。 - public static EncodedString Sign(string privateKeyPem, string messageData, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static EncodedString SignWithSHA256(string privateKeyPem, string messageData) { if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem)); if (messageData is null) throw new ArgumentNullException(nameof(messageData)); byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem); byte[] messageBytes = EncodedString.FromLiteralString(messageData); - byte[] signBytes = Sign(privateKeyBytes, messageBytes, digestAlgorithm); + byte[] signBytes = SignWithSHA256(privateKeyBytes, messageBytes); return EncodedString.ToBase64String(signBytes); } /// - /// 使用公钥验证签名。 + /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#1/PKCS#8 公钥字节数组。 /// 待验证的数据字节数组。 /// 签名字节数组。 - /// 签名算法。(默认值:) /// 验证结果。 - public static bool Verify(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes) { if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes)); if (signBytes is null) throw new ArgumentNullException(nameof(signBytes)); +#if NET5_0_OR_GREATER + using (RSA rsa = RSA.Create()) + { + rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _); + return rsa.VerifyData(messageBytes, signBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } +#else RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes); - return Verify(rsaPublicKeyParams, messageBytes, signBytes, digestAlgorithm); + return Verify(rsaPublicKeyParams, messageBytes, signBytes, DIGEST_ALGORITHM_SHA256); +#endif } /// - /// 使用公钥验证签名。 + /// 使用公钥基于 SHA-256 算法验证签名。 /// /// PKCS#1/PKCS#8 公钥(PEM 格式)。 /// 待验证的数据。 /// 经过编码后的(通常为 Base64)签名。 - /// 签名算法。(默认值:) /// 验证结果。 - public static bool Verify(string publicKeyPem, string messageData, EncodedString encodingSignature, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static bool VerifyWithSHA256(string publicKeyPem, string messageData, EncodedString encodingSignature) { if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem)); if (messageData is null) throw new ArgumentNullException(nameof(messageData)); @@ -225,7 +256,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem); byte[] messageBytes = EncodedString.FromLiteralString(messageData); byte[] signBytes = EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64); - return Verify(publicKeyBytes, messageBytes, signBytes, digestAlgorithm); + return VerifyWithSHA256(publicKeyBytes, messageBytes, signBytes); } /// @@ -234,20 +265,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 证书内容(PEM 格式)。 /// 待验证的数据。 /// 经过编码后的(通常为 Base64)签名。 - /// 签名算法。(默认值:) /// 验证结果。 - public static bool VerifyByCertificate(string certificatePem, string messageData, EncodedString encodingSignature, string digestAlgorithm = DIGEST_ALGORITHM_SHA256) + public static bool VerifyWithSHA256ByCertificate(string certificatePem, string messageData, EncodedString encodingSignature) { if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem)); string publicKeyPem = ExportPublicKeyFromCertificate(certificatePem); - return Verify(publicKeyPem, messageData, encodingSignature, digestAlgorithm); + return VerifyWithSHA256(publicKeyPem, messageData, encodingSignature); } /// /// 使用私钥基于 ECB 模式解密数据。 /// - /// PKCS#1/PKCS#8 私钥字节数组。 + /// PKCS#8 私钥字节数组。 /// 待解密的数据字节数组。 /// 填充模式。(默认值:) /// 解密后的数据字节数组。 @@ -256,7 +286,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes)); - RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes); +#if NET5_0_OR_GREATER + if (string.Equals(paddingMode, PADDING_MODE_PKCS1, StringComparison.OrdinalIgnoreCase)) + { + using (RSA rsa = RSA.Create()) + { + rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); + return rsa.Decrypt(cipherBytes, RSAEncryptionPadding.Pkcs1); + } + } +#endif + + RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode); } @@ -290,6 +331,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes)); +#if NET5_0_OR_GREATER + if (string.Equals(paddingMode, PADDING_MODE_PKCS1, StringComparison.OrdinalIgnoreCase)) + { + using (RSA rsa = RSA.Create()) + { + rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _); + return rsa.Encrypt(plainBytes, RSAEncryptionPadding.Pkcs1); + } + } +#endif RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes); return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingMode); } @@ -360,8 +411,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate is null) throw new ArgumentNullException(nameof(certificate)); +#if NET5_0_OR_GREATER + using (System.Security.Cryptography.X509Certificates.X509Certificate2 x509cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(ConvertCertificatePemToByteArray(certificate))) + { + return x509cert.SerialNumber; + } +#else X509Certificate x509cert = ParseCertificatePemToX509(certificate); return x509cert.SerialNumber.ToString(16); +#endif } /// @@ -373,8 +431,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate is null) throw new ArgumentNullException(nameof(certificate)); +#if NET5_0_OR_GREATER + using (System.Security.Cryptography.X509Certificates.X509Certificate2 x509cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(ConvertCertificatePemToByteArray(certificate))) + { + return new DateTimeOffset(x509cert.NotBefore); + } +#else X509Certificate x509cert = ParseCertificatePemToX509(certificate); return new DateTimeOffset(x509cert.NotBefore); +#endif } /// @@ -386,8 +451,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate is null) throw new ArgumentNullException(nameof(certificate)); +#if NET5_0_OR_GREATER + using (System.Security.Cryptography.X509Certificates.X509Certificate2 x509cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(ConvertCertificatePemToByteArray(certificate))) + { + return new DateTimeOffset(x509cert.NotAfter); + } +#else X509Certificate x509cert = ParseCertificatePemToX509(certificate); return new DateTimeOffset(x509cert.NotAfter); +#endif } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs index cbc9a95e..87326435 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs @@ -51,7 +51,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return Convert.FromBase64String(publicKeyPem); } - private static X509Certificate ConvertCertificatePemToX509(string certificatePem) + private static X509Certificate ParseCertificatePemToX509(string certificatePem) { using (TextReader sreader = new StringReader(certificatePem)) { @@ -726,7 +726,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities using (TextWriter swriter = new StringWriter()) { - X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem); + X509Certificate x509cert = ParseCertificatePemToX509(certificatePem); ECPublicKeyParameters exPublicKeyParams = (ECPublicKeyParameters)x509cert.GetPublicKey(); PemWriter pemWriter = new PemWriter(swriter); pemWriter.WriteObject(exPublicKeyParams); @@ -744,7 +744,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem)); - X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem); + X509Certificate x509cert = ParseCertificatePemToX509(certificatePem); return x509cert.SerialNumber.ToString(16); } @@ -757,7 +757,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem)); - X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem); + X509Certificate x509cert = ParseCertificatePemToX509(certificatePem); return new DateTimeOffset(x509cert.NotBefore); } @@ -770,7 +770,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem)); - X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem); + X509Certificate x509cert = ParseCertificatePemToX509(certificatePem); return new DateTimeOffset(x509cert.NotAfter); } diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/AESUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/AESUtility.cs index d5950433..e5a150d9 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/AESUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/AESUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Cryptography; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; @@ -39,18 +40,39 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities if (cipherBytes is 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($"AES/GCM/{paddingMode}"); - ICipherParameters cipherParams = new AeadParameters( - new KeyParameter(keyBytes), - TAG_LENGTH_BYTE * 8, - nonceBytes, - associatedDataBytes - ); - 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; +#if NET5_0_OR_GREATER + if (AesGcm.IsSupported) + { + if (!string.Equals(paddingMode, PADDING_MODE_NOPADDING, StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException(); + + using (AesGcm aes = new AesGcm(keyBytes)) + { + byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE]; + byte[] tagBytes = new byte[TAG_LENGTH_BYTE]; + Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length); + Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length); + + byte[] plainBytes = new byte[cipherWithoutTagBytes.Length]; + aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes); + return plainBytes; + } + } +#endif + { + IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}"); + ICipherParameters cipherParams = new AeadParameters( + new KeyParameter(keyBytes), + TAG_LENGTH_BYTE * 8, + nonceBytes, + associatedDataBytes + ); + 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; + } } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/RSAUtility.cs index e983d4c7..43148430 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Utilities/RSAUtility.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using System.Text.RegularExpressions; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; @@ -22,7 +23,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem) { - if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----")) + const string PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; + const string PKCS8_FOOTER = "-----END PRIVATE KEY-----"; + + if (!privateKeyPem.StartsWith(PKCS8_HEADER)) { using (TextReader textReader = new StringReader(privateKeyPem)) using (PemReader pemReader = new PemReader(textReader)) @@ -55,8 +59,8 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities } privateKeyPem = privateKeyPem - .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) - .Replace("-----END PRIVATE KEY-----", string.Empty); + .Replace(PKCS8_HEADER, string.Empty) + .Replace(PKCS8_FOOTER, string.Empty); privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty); return Convert.FromBase64String(privateKeyPem); } @@ -71,7 +75,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities /// /// 使用私钥基于 ECB 模式解密数据。 /// - /// PKCS#1/PKCS#8 私钥字节数组。 + /// PKCS#8 私钥字节数组。 /// 待解密的数据字节数组。 /// 填充模式。(默认值:) /// 解密后的数据字节数组。 @@ -80,6 +84,17 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes)); +#if NET5_0_OR_GREATER + if (string.Equals(paddingMode, PADDING_MODE_PKCS1, StringComparison.OrdinalIgnoreCase)) + { + using (RSA rsa = RSA.Create()) + { + rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); + return rsa.Decrypt(cipherBytes, RSAEncryptionPadding.Pkcs1); + } + } +#endif + RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode); } diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_ToolsRSAUtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_ToolsRSAUtilityTests.cs index 35d8ef94..15bc5409 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_ToolsRSAUtilityTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_ToolsRSAUtilityTests.cs @@ -57,14 +57,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests public void TestRSASignatureSHA256WithRSASign() { string msgText = "SHA256WithRSASignTest"; - string actualSignByPrivateKeyPkcs8 = Utilities.RSAUtility.Sign(RSA_PEM_PRIVATE_KEY_PKCS8, msgText)!; - string actualSignByPrivateKeyPkcs1 = Utilities.RSAUtility.Sign(RSA_PEM_PRIVATE_KEY_PKCS1, msgText)!; + string actualSignByPrivateKeyPkcs8 = Utilities.RSAUtility.SignWithSHA256(RSA_PEM_PRIVATE_KEY_PKCS8, msgText)!; + string actualSignByPrivateKeyPkcs1 = Utilities.RSAUtility.SignWithSHA256(RSA_PEM_PRIVATE_KEY_PKCS1, msgText)!; string expectedSign = "EzeVEIoBhzOhXpwbXdJjIuGIGRc6ArKO7sVo2fuAdzYTDgorAEufEnw7lPPXV1GTfFcHOnsAJH9kGJmg7Orwlkh7x7TyOGrkMEhWGulA9SIdmou4iBsHpIZ/TERSgGt/VTmqmfpkzJqrvbQWIQENwo7Lr6uJSJBND0YT3nIBP8TzbO3cHnQb6chHIBHrDF5vOO7HHu+Cga2MZnAtRizhO8BhK0jOmyro32CgIML3EVX8yuPy0kOk6aN1R8xFblZUD4NU2M6zzQpydmxaHr9B1WNFoMwmpoAS5BuKJMYOHO5cc6DhB+0fAGxaWtKp6759KhKCf8M65zh3WKS4l262SGuWq4qG1+AKf2DOgCon769+A4z8flOmvl0iIwoH9FThGJoP156rpAJW7v/bWputSeC6WToUTBRmGWdwWySVwW5AZ26OAFFWs1CmrGp3jF5E2oUy1mQwgfM0QN6DW+wD769ggIYH9HLHqDHbF5UyF7eNh3s8Qy23xXEKZWNMAJ0IdtdMQ7FRRgLFSCai7HELLlBJSCz7P5WTeYZGQpbvnUShkRvzujjO6XlGiKKI0EwKb121z8N6KRpvs4SnRztWBGoXbzHZgnXKXU/BWWADemqB2cvaT3Bj0k3/N3sea0dAEtlNEklRWoyyNUUlscK9zg4LBlHrhbbFo66uuub8ySo="; Assert.Equal(expectedSign, actualSignByPrivateKeyPkcs8); Assert.Equal(expectedSign, actualSignByPrivateKeyPkcs1); - Assert.True(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)actualSignByPrivateKeyPkcs8)); - Assert.True(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)actualSignByPrivateKeyPkcs1)); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)actualSignByPrivateKeyPkcs8)); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)actualSignByPrivateKeyPkcs1)); } [Fact(DisplayName = "测试用例:SHA256WithRSA 签名验证")] @@ -73,12 +73,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests string msgText = "SHA256WithRSAVerifyTest"; string signText = "aHX+MrmZHDEraMKBEPV2Vnps1B9b25lGbv/rdppx/S7+oaXtjKJprzCq5H7RCpvrKS3xYIeTEPwQGC3Vots7dCdLi8v8ew1vvtXf8qNAnd7CTMHqu3wSohXzgyASTmNbXE2ml9LbWYPPYMvPJXROQbGVjoOrsErWBPPJYXuO3lIckIfwI05OTdl4H3+BvpD/ZoljRp8Qgo9+paGvarBc++TaAh0FXnQf0TGNFUIeHHiAKBee5oCBTuZZM9J5RPw0oIq/g7Wun+e/zWiwVBPHltOgZrV46uagSAE6nBDHk+hlNxDivCxkJdBVCSIYFFmBXIcnGZ/u4ZfBui/k1jGoKibyvPK4z2+6GSlj41Yo81kuSBfzLiSsx33EPR1eIJJkwDTsvap0ymL9pfIqMiLuiteH5kGmL/dyONy9oAJywLEeITfoVyElM/CY6Dc+xDhRnjN7Hu54meYyXRZrnCtQ3YhzEr1immNBn6npgA/qi9aHsuWFOw8b8aSwOHDHTDmjmvV+axI8CVMrR0MjB9QNCWrKLq2B9iQX9MtLgcUyDsQvzAsxUJm/OEfzUjs9SHvmgmyAvzNAuTdO7wLQ+ZmKg0yZne6nvcrJVvfh3lD5ZPt7NY57Y6OIJluqKUT5H+a3H6W9Q1Z+cBMnHGYaaK7Tv8IcDdEYqTIG8hc5BqjFOzE="; - Assert.True(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)signText)); - Assert.True(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)signText)); - Assert.False(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)"FAKE SIGN")); - Assert.False(Utilities.RSAUtility.Verify(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)"FAKE SIGN")); - Assert.True(Utilities.RSAUtility.VerifyByCertificate(RSA_PEM_CERTIFICATE, msgText, (EncodedString)signText)); - Assert.False(Utilities.RSAUtility.VerifyByCertificate(RSA_PEM_CERTIFICATE, msgText, (EncodedString)"FAKE SIGN")); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)signText)); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)signText)); + Assert.False(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS8, msgText, (EncodedString)"FAKE SIGN")); + Assert.False(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY_PKCS1, msgText, (EncodedString)"FAKE SIGN")); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256ByCertificate(RSA_PEM_CERTIFICATE, msgText, (EncodedString)signText)); + Assert.False(Utilities.RSAUtility.VerifyWithSHA256ByCertificate(RSA_PEM_CERTIFICATE, msgText, (EncodedString)"FAKE SIGN")); } [Fact(DisplayName = "测试用例:使用 RSA 公钥加密")]