refactor(tenpayv3): 优化反射性能

This commit is contained in:
Fu Diwei
2024-02-04 11:39:32 +08:00
committed by RHQYZ
parent 1eef179f0a
commit 6630c357ca
4 changed files with 357 additions and 389 deletions

View File

@@ -6,150 +6,10 @@ using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
public static class WechatTenpayClientRequestEncryptionExtensions
{
private static TRequest InnerEncryptRequestSensitivePropertyByRSA<TRequest>(WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
{
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(attr.Scheme));
if (attribute is null)
return (false, oldValue);
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
string certificate;
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
{
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
.Where(e => CertificateEntry.ALGORITHM_TYPE_RSA.Equals(e.AlgorithmType))
.OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform certificates first.");
}
CertificateEntry entry = entries.First();
certificate = entry.Certificate;
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform certificates first.");
}
certificate = entry.Value.Certificate;
}
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = Utilities.RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue
);
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = Utilities.RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue,
paddingMode: "PKCS1PADDING"
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
return (true, newValue);
});
return request;
}
private static TRequest InnerEncryptRequestSensitivePropertyBySM<TRequest>(WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
{
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(attr.Scheme));
if (attribute is null)
return (false, oldValue);
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
string certificate;
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
{
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
.Where(e => CertificateEntry.ALGORITHM_TYPE_SM2.Equals(e.AlgorithmType))
.OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform certificates first.");
}
CertificateEntry entry = entries.First();
certificate = entry.Certificate;
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform certificates first.");
}
certificate = entry.Value.Certificate;
}
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = Utilities.SM2Utility.EncryptByCertificate(
certificate: certificate,
plainText: oldValue,
asn1Encoding: true
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
return (true, newValue);
});
return request;
}
/// <summary>
/// <para>加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
/// </summary>
@@ -164,19 +24,98 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
try
{
// 遍历并加密被标记为敏感数据的字段
bool requireEncrypt = Attribute.IsDefined(request.GetType(), typeof(WechatTenpaySensitiveAttribute));
if (requireEncrypt)
{
switch (client.Credentials.SignScheme)
{
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
return InnerEncryptRequestSensitivePropertyByRSA(client, request);
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!requireEncrypt)
return request;
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
return InnerEncryptRequestSensitivePropertyBySM(client, request);
string signScheme = client.Credentials.SignScheme;
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string certificate;
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
{
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
.Where(e => e.AlgorithmType == algorithmType)
.OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
CertificateEntry entry = entries.First();
certificate = entry.Certificate;
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
}
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = client.PlatformCertificateManager?.GetEntry(request.WechatpayCertificateSerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
certificate = entry.Value.Certificate;
}
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue
);
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue,
paddingMode: "PKCS1PADDING"
);
}
break;
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = SM2Utility.EncryptByCertificate(
certificate: certificate,
plainText: oldValue,
asn1Encoding: true
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
return (true, newValue);
});
return request;
}

View File

@@ -5,11 +5,22 @@ using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
public static class WechatTenpayClientResponseDecryptionExtensions
{
private static TResponse InnerDecryptResponseSensitiveProperty<TResponse>(WechatTenpayClient client, TResponse response)
where TResponse : Models.QueryCertificatesResponse
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static QueryCertificatesResponse DecryptResponseSensitiveProperty(this WechatTenpayClient client, QueryCertificatesResponse 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;
@@ -25,7 +36,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
certificate.EncryptCertificate.CipherText = Utilities.AESUtility.DecryptWithGCM(
certificate.EncryptCertificate.CipherText = AESUtility.DecryptWithGCM(
key: client.Credentials.MerchantV3Secret,
nonce: certificate.EncryptCertificate.Nonce,
aad: certificate.EncryptCertificate.AssociatedData,
@@ -41,11 +52,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要256bit的前 128bit。
byte[] secretBytes = Utilities.SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
byte[] secretBytes = SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
byte[] keyBytes = new byte[16];
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
byte[] plainBytes = Utilities.SM4Utility.DecryptWithGCM(
byte[] plainBytes = SM4Utility.DecryptWithGCM(
keyBytes: keyBytes,
nonceBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.Nonce),
aadBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.AssociatedData ?? string.Empty),
@@ -65,85 +76,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
return response;
}
private static TResponse InnerDecryptResponseSensitivePropertyByRSA<TResponse>(WechatTenpayClient client, TResponse response)
where TResponse : WechatTenpayResponse
{
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
{
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(attr.Scheme));
if (attribute is null)
return (false, oldValue);
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = Utilities.RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue
);
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = Utilities.RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
paddingMode: "PKCS1PADDING"
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
return (true, newValue);
});
return response;
}
private static TResponse InnerDecryptResponseSensitivePropertyBySM<TResponse>(WechatTenpayClient client, TResponse response)
where TResponse : WechatTenpayResponse
{
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
{
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(attr.Scheme));
if (attribute is null)
return (false, oldValue);
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = Utilities.SM2Utility.Decrypt(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
asn1Encoding: true
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
return (true, newValue);
});
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
/// </summary>
@@ -162,24 +94,69 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
try
{
// [GET] /certificates 接口的响应模型需特殊处理
if (response is Models.QueryCertificatesResponse queryCertificatesResponse)
if (response is QueryCertificatesResponse queryCertificatesResponse)
{
return (InnerDecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!;
return (DecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!;
}
// 遍历并解密被标记为敏感数据的字段
bool requireDecrypt = Attribute.IsDefined(response.GetType(), typeof(WechatTenpaySensitiveAttribute));
if (requireDecrypt)
{
switch (client.Credentials.SignScheme)
{
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
return InnerDecryptResponseSensitivePropertyByRSA(client, response);
if (!requireDecrypt)
return response;
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
return InnerDecryptResponseSensitivePropertyBySM(client, response);
string signScheme = client.Credentials.SignScheme;
ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string newValue;
switch (attribute.Algorithm)
{
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue
);
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
paddingMode: "PKCS1PADDING"
);
}
break;
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = SM2Utility.Decrypt(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
asn1Encoding: true
);
}
break;
default:
{
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
}
}
return (true, newValue);
});
return response;
}