mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-12-20 03:49:56 +08:00
refactor(tenpaybusiness): 优化加解密及哈希算法工具类
This commit is contained in:
@@ -5,6 +5,6 @@
|
||||
/// <summary>
|
||||
/// SHA256withRSA。
|
||||
/// </summary>
|
||||
public const string SHA245_WITH_RSA = "SHA256withRSA";
|
||||
public const string SHA256_WITH_RSA = "SHA256withRSA";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
webhookNonce: webhookNonce,
|
||||
webhookBody: webhookBody,
|
||||
webhookSignature: webhookSignature,
|
||||
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
|
||||
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
|
||||
webhookSerialNumber: webhookSerialNumber,
|
||||
out _
|
||||
);
|
||||
@@ -112,7 +112,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
webhookNonce: webhookNonce,
|
||||
webhookBody: webhookBody,
|
||||
webhookSignature: webhookSignature,
|
||||
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
|
||||
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
|
||||
webhookSerialNumber: webhookSerialNumber,
|
||||
out error
|
||||
);
|
||||
|
||||
@@ -6,6 +6,8 @@ using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
public static class WechatTenpayBusinessClientExecuteFileUploadsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -25,7 +27,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
|
||||
|
||||
if (request.FileHash is null)
|
||||
request.FileHash = BitConverter.ToString(Utilities.SM3Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
|
||||
request.FileHash = EncodedString.ToHexString(Utilities.SM3Utility.Hash(request.FileBytes)).Value!.ToLower();
|
||||
|
||||
if (request.FileContentType is null)
|
||||
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
public static class WechatTenpayBusinessClientRequestEncryptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,36 +21,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
|
||||
try
|
||||
{
|
||||
bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
|
||||
if (requireEncrypt)
|
||||
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpayBusinessSensitiveAttribute));
|
||||
if (!requireEncrypt)
|
||||
return request;
|
||||
|
||||
if (request.WechatpayEncryption is null)
|
||||
request.WechatpayEncryption = new WechatTenpayBusinessRequestEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm };
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.WechatpayEncryption.Algorithm))
|
||||
{
|
||||
if (request.WechatpayEncryption is null)
|
||||
request.WechatpayEncryption = new WechatTenpayBusinessRequestEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm };
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.WechatpayEncryption.Algorithm))
|
||||
Utilities.ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||
{
|
||||
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
|
||||
{
|
||||
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
|
||||
if (attr is null)
|
||||
return (false, oldValue);
|
||||
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpayBusinessSensitivePropertyAttribute)))
|
||||
return (false, oldValue);
|
||||
|
||||
string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!;
|
||||
string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!;
|
||||
string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKey: client.Credentials.TBEPCertificatePublicKey, plainText: sm4Key);
|
||||
string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!;
|
||||
string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!;
|
||||
string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKeyPem: client.Credentials.TBEPCertificatePublicKey, plainData: sm4Key).Value!;
|
||||
|
||||
request.WechatpayEncryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber;
|
||||
request.WechatpayEncryption.EncryptedKey = sm4EncryptedKey;
|
||||
request.WechatpayEncryption.IV = sm4IV;
|
||||
request.WechatpayEncryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber;
|
||||
request.WechatpayEncryption.EncryptedKey = sm4EncryptedKey;
|
||||
request.WechatpayEncryption.IV = sm4IV;
|
||||
|
||||
string newValue = Utilities.SM4Utility.EncryptWithCBC(key: sm4Key, iv: sm4IV, plainText: oldValue);
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new WechatTenpayBusinessException($"Failed to encrypt request. Unsupported encryption algorithm: \"{request.WechatpayEncryption.Algorithm}\".");
|
||||
}
|
||||
string newValue = Utilities.SM4Utility.EncryptWithCBC(encodingKey: new EncodedString(sm4Key, EncodingKinds.Base64), encodingIV: new EncodedString(sm4IV, EncodingKinds.Base64), plainData: oldValue).Value!;
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new WechatTenpayBusinessException($"Failed to encrypt request. Unsupported encryption algorithm: \"{request.WechatpayEncryption.Algorithm}\".");
|
||||
}
|
||||
}
|
||||
catch (WechatTenpayBusinessException)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
public static class WechatTenpayBusinessClientResponseDecryptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,41 +24,40 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
|
||||
try
|
||||
{
|
||||
bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
|
||||
if (requireDecrypt)
|
||||
bool requireDecrypt = response.GetType().IsDefined(typeof(WechatTenpayBusinessSensitiveAttribute));
|
||||
if (!requireDecrypt)
|
||||
return response;
|
||||
|
||||
if (response.WechatpayEncryption is null)
|
||||
throw new InvalidOperationException("Failed to decrypt response, because the value of \"TBEP-Encrypt\" is empty.");
|
||||
if (response.WechatpayEncryption.PlatformId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.PlatformCertificateSerialNumber)
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response, because the platform certificate with serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
|
||||
if (response.WechatpayEncryption.EnterpriseId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.EnterpriseCertificateSerialNumber)
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response, because the enterprise certificate serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(response.WechatpayEncryption.Algorithm))
|
||||
{
|
||||
if (response.WechatpayEncryption is null)
|
||||
throw new InvalidOperationException("Failed to decrypt response, because the value of \"TBEP-Encrypt\" is empty.");
|
||||
if (response.WechatpayEncryption.PlatformId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.PlatformCertificateSerialNumber)
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response, because the platform certificate with serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
|
||||
if (response.WechatpayEncryption.EnterpriseId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.EnterpriseCertificateSerialNumber)
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response, because the enterprise certificate serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(response.WechatpayEncryption.Algorithm))
|
||||
Utilities.ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) =>
|
||||
{
|
||||
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
||||
{
|
||||
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
|
||||
if (attr is null)
|
||||
return (false, oldValue);
|
||||
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpayBusinessSensitivePropertyAttribute)))
|
||||
return (false, oldValue);
|
||||
|
||||
string sm4EncryptedKey = response.WechatpayEncryption.EncryptedKey!;
|
||||
string sm4Key = Utilities.RSAUtility.DecryptWithECB(
|
||||
privateKey: response.WechatpayEncryption.PlatformId is not null ? client.Credentials.PlatformCertificatePrivateKey! :
|
||||
response.WechatpayEncryption.EnterpriseId is not null ? client.Credentials.EnterpriseCertificatePrivateKey! :
|
||||
string.Empty,
|
||||
cipherText: sm4EncryptedKey
|
||||
);
|
||||
string sm4IV = response.WechatpayEncryption.IV!;
|
||||
string sm4EncryptedKey = response.WechatpayEncryption.EncryptedKey!;
|
||||
string sm4Key = Utilities.RSAUtility.DecryptWithECB(
|
||||
privateKeyPem: response.WechatpayEncryption.PlatformId is not null ? client.Credentials.PlatformCertificatePrivateKey! :
|
||||
response.WechatpayEncryption.EnterpriseId is not null ? client.Credentials.EnterpriseCertificatePrivateKey! :
|
||||
string.Empty,
|
||||
encodingCipher: new EncodedString(sm4EncryptedKey, EncodingKinds.Base64)
|
||||
)!;
|
||||
string sm4IV = response.WechatpayEncryption.IV!;
|
||||
|
||||
string newValue = Utilities.SM4Utility.DecryptWithCBC(key: sm4Key, iv: sm4IV, cipherText: oldValue);
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response. Unsupported encryption algorithm: \"{response.WechatpayEncryption.Algorithm}\".");
|
||||
}
|
||||
string newValue = Utilities.SM4Utility.DecryptWithCBC(encodingKey: new EncodedString(sm4Key, EncodingKinds.Base64), encodingIV: new EncodedString(sm4IV, EncodingKinds.Base64), encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64)).Value!;
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new WechatTenpayBusinessException($"Failed to decrypt response. Unsupported encryption algorithm: \"{response.WechatpayEncryption.Algorithm}\".");
|
||||
}
|
||||
}
|
||||
catch (WechatTenpayBusinessException)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
@@ -65,7 +64,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
responseNonce: responseNonce,
|
||||
responseBody: responseBody,
|
||||
responseSignature: responseSignature,
|
||||
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
|
||||
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
|
||||
responseSerialNumber: responseSerialNumber,
|
||||
out _
|
||||
);
|
||||
@@ -115,7 +114,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
responseNonce: responseNonce,
|
||||
responseBody: responseBody,
|
||||
responseSignature: responseSignature,
|
||||
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
|
||||
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
|
||||
responseSerialNumber: responseSerialNumber,
|
||||
out error
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Linq;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
internal static class WechatTenpayBusinessClientSigningExtensions
|
||||
{
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strContent, out Exception? error)
|
||||
@@ -53,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
|
||||
switch (strSignatureAlgorithm)
|
||||
{
|
||||
case Constants.SignAlgorithms.SHA245_WITH_RSA:
|
||||
case Constants.SignAlgorithms.SHA256_WITH_RSA:
|
||||
{
|
||||
if (client.Credentials.TBEPCertificateSerialNumber is not null &&
|
||||
client.Credentials.TBEPCertificatePublicKey is not null)
|
||||
@@ -67,10 +69,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
}
|
||||
|
||||
error = null;
|
||||
return Utilities.RSAUtility.VerifyWithSHA256(
|
||||
publicKey: client.Credentials.TBEPCertificatePublicKey,
|
||||
return Utilities.RSAUtility.Verify(
|
||||
publicKeyPem: client.Credentials.TBEPCertificatePublicKey,
|
||||
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||
signature: strSignature
|
||||
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -7,6 +7,8 @@ using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
internal class WechatTenpayBusinessRequestSigningInterceptor : HttpInterceptor
|
||||
{
|
||||
private const string HTTP_HEADER_PLATFORM_AUTHORIZATION = HttpHeaders.Authorization;
|
||||
@@ -64,17 +66,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
|
||||
|
||||
switch (_signAlg)
|
||||
{
|
||||
case Constants.SignAlgorithms.SHA245_WITH_RSA:
|
||||
case Constants.SignAlgorithms.SHA256_WITH_RSA:
|
||||
{
|
||||
try
|
||||
{
|
||||
signText = Utilities.RSAUtility.SignWithSHA256(_platformCertPk, plainText);
|
||||
signText = Utilities.RSAUtility.Sign(_platformCertPk, plainText).Value!;
|
||||
|
||||
if (softSignRequired)
|
||||
{
|
||||
byte[] keyBytes = Convert.FromBase64String(_enterpriseCertPk!);
|
||||
byte[] msgBytes = Convert.FromBase64String(signText);
|
||||
softSignText = Convert.ToBase64String(Utilities.RSAUtility.SignWithSHA256(keyBytes, msgBytes));
|
||||
byte[] keyBytes = EncodedString.FromBase64String(_enterpriseCertPk!);
|
||||
byte[] msgBytes = EncodedString.FromBase64String(signText);
|
||||
softSignText = EncodedString.ToBase64String(Utilities.RSAUtility.Sign(keyBytes, msgBytes)).Value!;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,203 +1,284 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// RSA 算法工具类。
|
||||
/// </summary>
|
||||
public static class RSAUtility
|
||||
{
|
||||
private const string RSA_CIPHER_ALGORITHM_ECB = "RSA/ECB";
|
||||
private const string RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1 = "OAEPWITHSHA1ANDMGF1PADDING";
|
||||
private const string RSA_SIGNER_ALGORITHM_SHA256 = "SHA-256withRSA";
|
||||
/// <summary>
|
||||
/// 填充模式:OAEPwithSHA-256andMGF1Padding。
|
||||
/// </summary>
|
||||
public const string PADDING_MODE_OAEPWITHSHA1ANDMGF1 = "OAEPWITHSHA1ANDMGF1PADDING";
|
||||
|
||||
private static byte[] ConvertPrivateKeyPkcs8PemToByteArray(string privateKey)
|
||||
/// <summary>
|
||||
/// 签名算法:SHA-256withRSA。
|
||||
/// </summary>
|
||||
public const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA";
|
||||
|
||||
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
|
||||
{
|
||||
privateKey = privateKey
|
||||
if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----"))
|
||||
{
|
||||
using (TextReader textReader = new StringReader(privateKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
{
|
||||
object pemObject = pemReader.ReadObject();
|
||||
|
||||
if (pemObject is AsymmetricCipherKeyPair)
|
||||
{
|
||||
// PKCS#1 格式
|
||||
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemObject;
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
{
|
||||
Pkcs8Generator pkcs8 = new Pkcs8Generator(cipherKeyPair.Private);
|
||||
pemWriter.WriteObject(pkcs8);
|
||||
pemWriter.Writer.Close();
|
||||
|
||||
privateKeyPem = textWriter.ToString()!;
|
||||
}
|
||||
}
|
||||
else if (pemObject is RsaPrivateCrtKeyParameters)
|
||||
{
|
||||
// PKCS#8 格式
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Private key format is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
privateKeyPem = privateKeyPem
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
|
||||
.Replace("-----END PRIVATE KEY-----", string.Empty);
|
||||
privateKey = Regex.Replace(privateKey, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKey);
|
||||
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKeyPem);
|
||||
}
|
||||
|
||||
private static byte[] ConvertPublicKeyPkcs8PemToByteArray(string publicKey)
|
||||
private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem)
|
||||
{
|
||||
publicKey = publicKey
|
||||
if (!publicKeyPem.StartsWith("-----BEGIN PUBLIC KEY-----"))
|
||||
{
|
||||
using (TextReader textReader = new StringReader(publicKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
{
|
||||
object pemObject = pemReader.ReadObject();
|
||||
if (pemObject is RsaKeyParameters)
|
||||
{
|
||||
// PKCS#1 或 PKCS#8 格式
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)pemObject;
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
{
|
||||
pemWriter.WriteObject(rsaKeyParams);
|
||||
pemWriter.Writer.Close();
|
||||
|
||||
publicKeyPem = textWriter.ToString()!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Public key format is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publicKeyPem = publicKeyPem
|
||||
.Replace("-----BEGIN PUBLIC KEY-----", string.Empty)
|
||||
.Replace("-----END PUBLIC KEY-----", string.Empty);
|
||||
publicKey = Regex.Replace(publicKey, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(publicKey);
|
||||
publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(publicKeyPem);
|
||||
}
|
||||
|
||||
private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes)
|
||||
private static RsaKeyParameters ParsePrivateKeyToParameters(byte[] privateKeyBytes)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
|
||||
return (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
}
|
||||
|
||||
private static RsaKeyParameters ParsePublicKeyToParameters(byte[] publicKeyBytes)
|
||||
{
|
||||
return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
}
|
||||
|
||||
private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes, string digestAlgorithm)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(digestAlgorithm);
|
||||
signer.Init(true, rsaPrivateKeyParams);
|
||||
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
|
||||
return signer.GenerateSignature();
|
||||
}
|
||||
|
||||
private static bool VerifyWithSHA256(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, byte[] signBytes)
|
||||
private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, byte[] signBytes, string digestAlgorithm)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
|
||||
ISigner signer = SignerUtilities.GetSigner(digestAlgorithm);
|
||||
signer.Init(false, rsaPublicKeyParams);
|
||||
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
|
||||
return signer.VerifySignature(signBytes);
|
||||
}
|
||||
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingAlgorithm)
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"RSA/ECB/{paddingMode}");
|
||||
cipher.Init(false, rsaPrivateKeyParams);
|
||||
return cipher.DoFinal(cipherBytes);
|
||||
}
|
||||
|
||||
private static byte[] EncryptWithECB(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, string paddingAlgorithm)
|
||||
private static byte[] EncryptWithECB(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"RSA/ECB/{paddingMode}");
|
||||
cipher.Init(true, rsaPublicKeyParams);
|
||||
return cipher.DoFinal(msgBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// 使用私钥生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="privateKeyBytes">PKCS#1/PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="msgBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] msgBytes)
|
||||
public static byte[] Sign(byte[] privateKeyBytes, byte[] msgBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256)
|
||||
{
|
||||
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
|
||||
|
||||
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
return SignWithSHA256(rsaPrivateKeyParams, msgBytes);
|
||||
RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
|
||||
return Sign(rsaPrivateKeyParams, msgBytes, digestAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// 使用私钥生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="message">待签名的文本数据。</param>
|
||||
/// <returns>经 Base64 编码的签名。</returns>
|
||||
public static string SignWithSHA256(string privateKey, string message)
|
||||
/// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="message">待签名的数据。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>经过 Base64 编码的签名。</returns>
|
||||
public static EncodedString Sign(string privateKeyPem, string message, string digestAlgorithm = DIGEST_ALGORITHM_SHA256)
|
||||
{
|
||||
if (privateKey is null) throw new ArgumentNullException(nameof(privateKey));
|
||||
if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem));
|
||||
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey);
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes);
|
||||
return Convert.ToBase64String(signBytes);
|
||||
byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
|
||||
byte[] msgBytes = EncodedString.FromLiteralString(message);
|
||||
byte[] signBytes = Sign(privateKeyBytes, msgBytes, digestAlgorithm);
|
||||
return EncodedString.ToBase64String(signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// 使用公钥验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
|
||||
/// <param name="msgBytes">待验证的数据字节数据。</param>
|
||||
/// <param name="signBytes">待验证的签名字节数据。</param>
|
||||
/// <param name="publicKeyBytes">PKCS#1/PKCS#8 公钥字节数组。</param>
|
||||
/// <param name="msgBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes)
|
||||
public static bool Verify(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes, string digestAlgorithm = DIGEST_ALGORITHM_SHA256)
|
||||
{
|
||||
if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
|
||||
if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
|
||||
|
||||
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes);
|
||||
RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
|
||||
return Verify(rsaPublicKeyParams, msgBytes, signBytes, digestAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// 使用公钥验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="message">待验证的文本数据。</param>
|
||||
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
|
||||
/// <param name="publicKeyPem">PKCS#1/PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="message">待验证的数据。</param>
|
||||
/// <param name="encodingSignature">经过编码后的(通常为 Base64)签名。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(string publicKey, string message, string signature)
|
||||
public static bool Verify(string publicKeyPem, string message, EncodedString encodingSignature, string digestAlgorithm = DIGEST_ALGORITHM_SHA256)
|
||||
{
|
||||
if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));
|
||||
if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem));
|
||||
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||
if (signature is null) throw new ArgumentNullException(nameof(signature));
|
||||
if (encodingSignature.Value is null) throw new ArgumentNullException(nameof(encodingSignature));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey);
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] signBytes = Convert.FromBase64String(signature);
|
||||
return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes);
|
||||
byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
|
||||
byte[] msgBytes = EncodedString.FromLiteralString(message);
|
||||
byte[] signBytes = EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
return Verify(publicKeyBytes, msgBytes, signBytes, digestAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 ECB 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数据。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="privateKeyBytes">PKCS#1/PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
|
||||
{
|
||||
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
|
||||
|
||||
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingAlgorithm);
|
||||
RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
|
||||
return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 ECB 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>解密后的文本数据。</returns>
|
||||
public static string DecryptWithECB(string privateKey, string cipherText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
/// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="encodingCipher">经过编码后的(通常为 Base64)待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/>)</param>
|
||||
/// <returns>解密后的数据。</returns>
|
||||
public static EncodedString DecryptWithECB(string privateKeyPem, EncodedString encodingCipher, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
|
||||
{
|
||||
if (privateKey is null) throw new ArgumentNullException(nameof(privateKey));
|
||||
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
|
||||
if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem));
|
||||
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey);
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingAlgorithm);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
|
||||
byte[] cipherBytes = EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode);
|
||||
return EncodedString.ToLiteralString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 ECB 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="publicKeyBytes">PKCS#1/PKCS#8 公钥字节数组。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithECB(byte[] publicKeyBytes, byte[] plainBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
public static byte[] EncryptWithECB(byte[] publicKeyBytes, byte[] plainBytes, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
|
||||
{
|
||||
if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingAlgorithm);
|
||||
RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
|
||||
return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 ECB 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>经 Base64 编码的加密数据。</returns>
|
||||
public static string EncryptWithECB(string publicKey, string plainText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
/// <param name="publicKeyPem">PKCS#1/PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="plainData">待加密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/>)</param>
|
||||
/// <returns>经过 Base64 编码的加密数据。</returns>
|
||||
public static EncodedString EncryptWithECB(string publicKeyPem, string plainData, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
|
||||
{
|
||||
if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));
|
||||
if (plainText is null) throw new ArgumentNullException(nameof(plainText));
|
||||
if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem));
|
||||
if (plainData is null) throw new ArgumentNullException(nameof(plainData));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingAlgorithm);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
|
||||
byte[] plainBytes = EncodedString.FromLiteralString(plainData);
|
||||
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingMode);
|
||||
return EncodedString.ToBase64String(cipherBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 算法工具类。
|
||||
/// </summary>
|
||||
public static class SHA256Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 SHA-256 信息摘要。
|
||||
/// 计算 SHA-256 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="bytes">信息字节数组。</param>
|
||||
/// <returns>信息摘要字节数组。</returns>
|
||||
/// <param name="bytes">要计算哈希值的信息字节数组。</param>
|
||||
/// <returns>哈希值字节数组。</returns>
|
||||
public static byte[] Hash(byte[] bytes)
|
||||
{
|
||||
if (bytes is null) throw new ArgumentNullException(nameof(bytes));
|
||||
|
||||
using SHA256 sha = SHA256.Create();
|
||||
return sha.ComputeHash(bytes);
|
||||
#if NET5_0_OR_GREATER
|
||||
return SHA256.HashData(bytes);
|
||||
#else
|
||||
using SHA256 sha256 = SHA256.Create();
|
||||
return sha256.ComputeHash(bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 SHA-256 信息摘要。
|
||||
/// 计算 SHA-256 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="message">文本信息。</param>
|
||||
/// <returns>信息摘要。</returns>
|
||||
public static string Hash(string message)
|
||||
/// <param name="message">要计算哈希值的信息。</param>
|
||||
/// <returns>经过十六进制编码的哈希值。</returns>
|
||||
public static EncodedString Hash(string message)
|
||||
{
|
||||
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] msgBytes = EncodedString.FromLiteralString(message);
|
||||
byte[] hashBytes = Hash(msgBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
||||
return EncodedString.ToHexString(hashBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// SM3 算法工具类。
|
||||
/// </summary>
|
||||
public static class SM3Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 SM3 哈希值。
|
||||
/// 计算 SM3 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="bytes">信息字节数组。</param>
|
||||
/// <returns>哈希字节数组。</returns>
|
||||
/// <param name="bytes">要计算哈希值的信息字节数组。</param>
|
||||
/// <returns>哈希值字节数组。</returns>
|
||||
public static byte[] Hash(byte[] bytes)
|
||||
{
|
||||
if (bytes is null) throw new ArgumentNullException(nameof(bytes));
|
||||
@@ -26,17 +27,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 SM3 哈希值。
|
||||
/// 计算 SM3 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="message">文本信息。</param>
|
||||
/// <returns>哈希值。</returns>
|
||||
public static string Hash(string message)
|
||||
/// <param name="message">要计算哈希值的信息。</param>
|
||||
/// <returns>经过十六进制编码的哈希值。</returns>
|
||||
public static EncodedString Hash(string message)
|
||||
{
|
||||
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] msgBytes = EncodedString.FromLiteralString(message);
|
||||
byte[] hashBytes = Hash(msgBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
||||
return EncodedString.ToHexString(hashBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,51 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// SM4 算法工具类。
|
||||
/// </summary>
|
||||
public static class SM4Utility
|
||||
{
|
||||
private const string SM4_ALGORITHM_NAME = "SM4";
|
||||
private const string SM4_CIPHER_ALGORITHM_CBC = "SM4/CBC";
|
||||
private const string SM4_CIPHER_PADDING_PKCS7PADDING = "PKCS7PADDING";
|
||||
/// <summary>
|
||||
/// 填充模式:PKCS7Padding。
|
||||
/// </summary>
|
||||
public const string PADDING_MODE_PKCS7 = "PKCS7PADDING";
|
||||
|
||||
private static byte[] DecryptWithCBC(ICipherParameters sm4KeyParams, byte[] cipherBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{SM4_CIPHER_ALGORITHM_CBC}/{paddingMode}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"SM4/CBC/{paddingMode}");
|
||||
cipher.Init(false, sm4KeyParams);
|
||||
return cipher.DoFinal(cipherBytes);
|
||||
}
|
||||
|
||||
private static byte[] EncryptWithCBC(ICipherParameters sm4KeyParams, byte[] plainBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{SM4_CIPHER_ALGORITHM_CBC}/{paddingMode}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"SM4/CBC/{paddingMode}");
|
||||
cipher.Init(true, sm4KeyParams);
|
||||
return cipher.DoFinal(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">密钥字节数组。</param>
|
||||
/// <param name="ivBytes">偏移量字节数据。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
|
||||
{
|
||||
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes);
|
||||
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
|
||||
return EncryptWithCBC(sm4keyParamsWithIv, plainBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="key">经 Base64 编码的密钥。</param>
|
||||
/// <param name="iv">>经 Base64 编码的偏移量。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>经 Base64 编码的加密数据。</returns>
|
||||
public static string EncryptWithCBC(string key, string iv, string plainText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
|
||||
{
|
||||
if (key is null) throw new ArgumentNullException(nameof(key));
|
||||
if (plainText is null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] keyBytes = Convert.FromBase64String(key);
|
||||
byte[] ivBytes = Convert.FromBase64String(iv);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithCBC(keyBytes, ivBytes, plainBytes, paddingMode);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">密钥字节数据。</param>
|
||||
/// <param name="ivBytes">偏移量字节数据。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <param name="keyBytes">密钥字节数组。</param>
|
||||
/// <param name="ivBytes">偏移量字节数组。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
|
||||
public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_PKCS7)
|
||||
{
|
||||
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (ivBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
|
||||
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes);
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
|
||||
return DecryptWithCBC(sm4keyParamsWithIv, cipherBytes, paddingMode);
|
||||
}
|
||||
@@ -88,21 +53,62 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="key">经 Base64 编码的密钥。</param>
|
||||
/// <param name="iv">>经 Base64 编码的偏移量。</param>
|
||||
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>解密后的文本数据。</returns>
|
||||
public static string DecryptWithCBC(string key, string iv, string cipherText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
|
||||
/// <param name="encodingKey">经过编码后的(通常为 Base64)密钥。</param>
|
||||
/// <param name="encodingIV">>经过编码后的(通常为 Base64)偏移量。</param>
|
||||
/// <param name="encodingCipher">经过编码后的(通常为 Base64)待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/>)</param>
|
||||
/// <returns>解密后的数据。</returns>
|
||||
public static EncodedString DecryptWithCBC(EncodedString encodingKey, EncodedString encodingIV, EncodedString encodingCipher, string paddingMode = PADDING_MODE_PKCS7)
|
||||
{
|
||||
if (key is null) throw new ArgumentNullException(nameof(key));
|
||||
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingIV.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
|
||||
|
||||
byte[] keyBytes = Convert.FromBase64String(key);
|
||||
byte[] ivBytes = Convert.FromBase64String(iv);
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] keyBytes = EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] ivBytes = EncodedString.FromString(encodingIV, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] cipherBytes = EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] plainBytes = DecryptWithCBC(keyBytes, ivBytes, cipherBytes, paddingMode);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
return EncodedString.ToLiteralString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">密钥字节数组。</param>
|
||||
/// <param name="ivBytes">偏移量字节数组。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes, string paddingMode = PADDING_MODE_PKCS7)
|
||||
{
|
||||
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (ivBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
|
||||
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
|
||||
return EncryptWithCBC(sm4keyParamsWithIv, plainBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="encodingKey">经过编码后的(通常为 Base64)密钥。</param>
|
||||
/// <param name="encodingIV">>经过编码后的(通常为 Base64)偏移量。</param>
|
||||
/// <param name="plainData">待加密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/>)</param>
|
||||
/// <returns>经过 Base64 编码的加密数据。</returns>
|
||||
public static EncodedString EncryptWithCBC(EncodedString encodingKey, EncodedString encodingIV, string plainData, string paddingMode = PADDING_MODE_PKCS7)
|
||||
{
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingIV.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (plainData is null) throw new ArgumentNullException(nameof(plainData));
|
||||
|
||||
byte[] keyBytes = EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] ivBytes = EncodedString.FromString(encodingIV, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
byte[] plainBytes = EncodedString.FromLiteralString(plainData);
|
||||
byte[] cipherBytes = EncryptWithCBC(keyBytes, ivBytes, plainBytes, paddingMode);
|
||||
return EncodedString.ToBase64String(cipherBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +1,122 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
internal static class ReflectionUtility
|
||||
internal static partial class ReflectionHelper
|
||||
{
|
||||
public delegate (bool Modified, string NewValue) ReplacePropertyStringValueReplacementHandler(object target, PropertyInfo currentProp, string oldValue);
|
||||
|
||||
public static void ReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
||||
public static void ReplaceObjectStringProperties(object targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref obj, replacement);
|
||||
}
|
||||
|
||||
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
||||
{
|
||||
if (obj is null) throw new ArgumentNullException(nameof(obj));
|
||||
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
||||
|
||||
Type objType = obj.GetType();
|
||||
if (!objType.IsClass)
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (objType.IsArray || obj is IList || obj is IDictionary)
|
||||
{
|
||||
InnerReplaceEachCollectionPropertyStringValue(ref obj, objType, replacement, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var childProp in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (!childProp.CanWrite)
|
||||
continue;
|
||||
|
||||
Type propType = childProp.PropertyType;
|
||||
if (propType == typeof(string))
|
||||
{
|
||||
string value = (string)childProp.GetValue(obj, null)!;
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
var result = replacement(obj, childProp, value);
|
||||
if (result.Modified)
|
||||
{
|
||||
childProp.SetValue(obj, result.NewValue);
|
||||
}
|
||||
}
|
||||
else if (propType.IsClass)
|
||||
{
|
||||
object? value = childProp.GetValue(obj, null);
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
InnerReplacePropertyStringValue(ref value, replacement);
|
||||
childProp.SetValue(obj, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
object? value = childProp.GetValue(obj, null);
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
InnerReplaceEachCollectionPropertyStringValue(ref value, propType, replacement, childProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
ISet<object> visited = new HashSet<object>(ReferenceEqualityComparer.Instance); // 处理循环引用问题
|
||||
InnerReplaceObjectStringProperties(ref targetObj, null, replacement, visited);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InnerReplaceEachCollectionPropertyStringValue<T>(ref T obj, Type objType, ReplacePropertyStringValueReplacementHandler replacement, PropertyInfo? currentProp)
|
||||
partial class ReflectionHelper
|
||||
{
|
||||
public delegate (bool IsModified, string NewValue) ReplaceObjectStringPropertiesReplacementDelegate(object currentObj, PropertyInfo? currentProp, string oldValue);
|
||||
|
||||
private static void InnerReplaceObjectStringProperties(ref object currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet<object> visited)
|
||||
{
|
||||
if (objType.IsArray)
|
||||
{
|
||||
var array = (obj as Array)!;
|
||||
if (currentObj is null) throw new ArgumentNullException(nameof(currentObj));
|
||||
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
||||
|
||||
for (int i = 0, len = array.Length; i < len; i++)
|
||||
if (!visited.Add(currentObj)) return;
|
||||
|
||||
Type type = currentObj.GetType();
|
||||
|
||||
// 跳过基元类型、枚举类型、抽象或接口类型,及部分 CLR 内置类型
|
||||
if (type.IsPrimitive ||
|
||||
type.IsEnum ||
|
||||
type.IsAbstract ||
|
||||
type.IsInterface ||
|
||||
type == typeof(Guid) ||
|
||||
type == typeof(DateTime) ||
|
||||
type == typeof(DateTimeOffset) ||
|
||||
#if NET5_0_OR_GREATER
|
||||
type == typeof(DateOnly) ||
|
||||
type == typeof(TimeOnly) ||
|
||||
#endif
|
||||
type == typeof(TimeSpan))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理数组类型
|
||||
if (type.IsArray)
|
||||
{
|
||||
Array currentObjAsArray = (Array)currentObj;
|
||||
for (int i = 0; i < currentObjAsArray.Length; i++)
|
||||
{
|
||||
object? element = array.GetValue(i);
|
||||
object? element = currentObjAsArray.GetValue(i);
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
Type elementType = element.GetType();
|
||||
if (elementType == typeof(string))
|
||||
{
|
||||
if (currentProp is null)
|
||||
if (currentObjAsArray.IsReadOnly)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
if ((string)element == string.Empty)
|
||||
continue;
|
||||
|
||||
var oldValue = (string)element!;
|
||||
var resHandler = replacement(obj!, currentProp, oldValue);
|
||||
if (resHandler.Modified && !array.IsReadOnly)
|
||||
{
|
||||
array.SetValue(resHandler.NewValue, i);
|
||||
}
|
||||
}
|
||||
else if (elementType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref element, replacement);
|
||||
//if (!array.IsReadOnly)
|
||||
//{
|
||||
// array.SetValue(element, i);
|
||||
//}
|
||||
var res = replacement(currentObjAsArray, currentProp, (string)element);
|
||||
if (res.IsModified)
|
||||
currentObjAsArray.SetValue(res.NewValue, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
InnerReplaceObjectStringProperties(ref element, currentProp, replacement, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is IList)
|
||||
{
|
||||
var list = (obj as IList)!;
|
||||
|
||||
for (int i = 0, len = list.Count; i < len; i++)
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理列表类型
|
||||
if (currentObj is IList)
|
||||
{
|
||||
IList currentObjAsList = (IList)currentObj;
|
||||
for (int i = 0; i < currentObjAsList.Count; i++)
|
||||
{
|
||||
object? element = list[i];
|
||||
object? element = currentObjAsList[i];
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
Type elementType = element.GetType();
|
||||
if (elementType == typeof(string))
|
||||
{
|
||||
if (currentProp is null)
|
||||
if (currentObjAsList.IsReadOnly)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
if ((string)element == string.Empty)
|
||||
continue;
|
||||
|
||||
var oldValue = (string)element!;
|
||||
var resHandler = replacement(obj, currentProp, oldValue);
|
||||
if (resHandler.Modified && !list.IsReadOnly)
|
||||
{
|
||||
list[i] = resHandler.NewValue;
|
||||
}
|
||||
}
|
||||
else if (elementType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref element, replacement);
|
||||
//if (!list.IsReadOnly)
|
||||
//{
|
||||
// list[i] = element;
|
||||
//}
|
||||
var res = replacement(currentObjAsList, currentProp, (string)element);
|
||||
if (res.IsModified)
|
||||
currentObjAsList[i] = res.NewValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
InnerReplaceObjectStringProperties(ref element, currentProp, replacement, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is IDictionary)
|
||||
{
|
||||
var dict = (obj as IDictionary)!;
|
||||
|
||||
foreach (DictionaryEntry entry in dict)
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理字典类型
|
||||
if (currentObj is IDictionary)
|
||||
{
|
||||
IDictionary currentObjAsDictionary = (IDictionary)currentObj;
|
||||
foreach (DictionaryEntry entry in currentObjAsDictionary)
|
||||
{
|
||||
object? entryValue = entry.Value;
|
||||
if (entryValue is null)
|
||||
@@ -160,32 +125,137 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
Type entryValueType = entryValue.GetType();
|
||||
if (entryValueType == typeof(string))
|
||||
{
|
||||
if (currentProp is null)
|
||||
if (currentObjAsDictionary.IsReadOnly)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
if ((string)entryValue == string.Empty)
|
||||
continue;
|
||||
|
||||
string oldValue = (string)entryValue!;
|
||||
var resHandler = replacement(obj, currentProp, oldValue);
|
||||
if (resHandler.Modified && !dict.IsReadOnly)
|
||||
{
|
||||
dict[entry.Key] = resHandler.NewValue;
|
||||
}
|
||||
}
|
||||
else if (entryValueType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref entryValue, replacement);
|
||||
//if (!dict.IsReadOnly)
|
||||
//{
|
||||
// dict[entry.Key] = entryValue;
|
||||
//}
|
||||
var res = replacement(currentObjAsDictionary, currentProp, (string)entryValue);
|
||||
if (res.IsModified)
|
||||
currentObjAsDictionary[entry.Key] = res.NewValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
InnerReplaceObjectStringProperties(ref entryValue, currentProp, replacement, visited);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历属性
|
||||
foreach (PropertyInfo property in GetWritableProperties(type))
|
||||
{
|
||||
Type propertyType = property.PropertyType;
|
||||
if (propertyType == typeof(string))
|
||||
{
|
||||
string? propertyValue = GetPropertyValue<string>(currentObj, property);
|
||||
if (propertyValue is null || propertyValue == string.Empty)
|
||||
continue;
|
||||
|
||||
var res = replacement(currentObj, property, propertyValue);
|
||||
if (res.IsModified)
|
||||
SetPropertyValue(currentObj, property, res.NewValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
object? propertyValue = GetPropertyValue<object>(currentObj, property);
|
||||
if (propertyValue is null)
|
||||
continue;
|
||||
|
||||
InnerReplaceObjectStringProperties(ref propertyValue, property, replacement, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class ReflectionHelper
|
||||
{
|
||||
private static readonly IDictionary<Type, PropertyInfo[]> _propsCache = new Dictionary<Type, PropertyInfo[]>(capacity: 128);
|
||||
private static readonly Hashtable _getterCache = new Hashtable(capacity: 128);
|
||||
private static readonly Hashtable _setterCache = new Hashtable(capacity: 128);
|
||||
|
||||
private static PropertyInfo[] GetWritableProperties(Type type)
|
||||
{
|
||||
if (type is null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (!_propsCache.TryGetValue(type, out PropertyInfo[]? properties))
|
||||
{
|
||||
properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(e => e.CanRead && e.CanWrite)
|
||||
.ToArray();
|
||||
_propsCache[type] = properties;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static T? GetPropertyValue<T>(object targetObj, PropertyInfo property)
|
||||
{
|
||||
// 提供比 PropertyInfo.GetValue() 更快的属性取值方法
|
||||
// 只可针对热点类型使用,否则可能会更慢
|
||||
|
||||
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
if (!property.CanRead)
|
||||
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a getter.");
|
||||
|
||||
Func<object, T>? getter = _getterCache[property] as Func<object, T>;
|
||||
if (getter is null)
|
||||
{
|
||||
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||
MemberExpression getPropertyValueExpr = Expression.Property(castTargetExpr, property);
|
||||
UnaryExpression castPropertyValueExpr = Expression.Convert(getPropertyValueExpr, typeof(T));
|
||||
getter = Expression.Lambda<Func<object, T>>(castPropertyValueExpr, targetExpr).Compile();
|
||||
_getterCache[property] = getter;
|
||||
}
|
||||
|
||||
return getter.Invoke(targetObj);
|
||||
}
|
||||
|
||||
private static void SetPropertyValue<T>(object targetObj, PropertyInfo property, T? value)
|
||||
{
|
||||
// 提供比 PropertyInfo.SetValue() 更快的属性赋值方法
|
||||
// 只可针对热点类型使用,否则可能会更慢
|
||||
|
||||
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
if (!property.CanWrite)
|
||||
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a setter.");
|
||||
|
||||
Action<object, T?>? setter = _setterCache[property] as Action<object, T?>;
|
||||
if (setter is null)
|
||||
{
|
||||
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||
ParameterExpression propertyValueExpr = Expression.Parameter(typeof(T));
|
||||
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||
UnaryExpression castPropertyValueExpr = Expression.Convert(propertyValueExpr, property.PropertyType);
|
||||
MethodCallExpression setPropertyValueExpr = Expression.Call(castTargetExpr, property.GetSetMethod()!, castPropertyValueExpr);
|
||||
setter = Expression.Lambda<Action<object, T?>>(setPropertyValueExpr, targetExpr, propertyValueExpr).Compile();
|
||||
_setterCache[property] = setter;
|
||||
}
|
||||
|
||||
setter.Invoke(targetObj, value);
|
||||
}
|
||||
}
|
||||
|
||||
partial class ReflectionHelper
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#else
|
||||
private sealed class ReferenceEqualityComparer : IEqualityComparer<object?>, IEqualityComparer
|
||||
{
|
||||
public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();
|
||||
|
||||
private ReferenceEqualityComparer() { }
|
||||
|
||||
public new bool Equals(object? x, object? y) => ReferenceEquals(x, y);
|
||||
|
||||
public int GetHashCode(object? obj) => RuntimeHelpers.GetHashCode(obj!);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
/// <summary>
|
||||
/// 获取或设置微企付 API 签名认证方式。
|
||||
/// <para>
|
||||
/// 默认值:<see cref="Constants.SignAlgorithms.SHA245_WITH_RSA"/>
|
||||
/// 默认值:<see cref="Constants.SignAlgorithms.SHA256_WITH_RSA"/>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA245_WITH_RSA;
|
||||
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA256_WITH_RSA;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付平台账号。
|
||||
|
||||
@@ -22,25 +22,43 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
|
||||
|
||||
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
|
||||
{
|
||||
using (TextReader textReader = new StringReader(privateKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----"))
|
||||
{
|
||||
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
using (TextReader textReader = new StringReader(privateKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
{
|
||||
Pkcs8Generator pkcs8 = new Pkcs8Generator(cipherKeyPair.Private);
|
||||
pemWriter.WriteObject(pkcs8);
|
||||
pemWriter.Writer.Close();
|
||||
object pemObject = pemReader.ReadObject();
|
||||
|
||||
privateKeyPem = textWriter.ToString()!;
|
||||
privateKeyPem = privateKeyPem
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
|
||||
.Replace("-----END PRIVATE KEY-----", string.Empty);
|
||||
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKeyPem);
|
||||
if (pemObject is AsymmetricCipherKeyPair)
|
||||
{
|
||||
// PKCS#1 格式
|
||||
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemObject;
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
{
|
||||
Pkcs8Generator pkcs8 = new Pkcs8Generator(cipherKeyPair.Private);
|
||||
pemWriter.WriteObject(pkcs8);
|
||||
pemWriter.Writer.Close();
|
||||
|
||||
privateKeyPem = textWriter.ToString()!;
|
||||
}
|
||||
}
|
||||
else if (pemObject is RsaPrivateCrtKeyParameters)
|
||||
{
|
||||
// PKCS#8 格式
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Private key format is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
privateKeyPem = privateKeyPem
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
|
||||
.Replace("-----END PRIVATE KEY-----", string.Empty);
|
||||
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKeyPem);
|
||||
}
|
||||
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingMode)
|
||||
|
||||
@@ -2,6 +2,8 @@ using Xunit;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
public class TestCase_ToolsSM4UtilityTests
|
||||
{
|
||||
[Fact(DisplayName = "测试用例:SM4 加密")]
|
||||
@@ -11,7 +13,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
|
||||
string plainText = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!";
|
||||
|
||||
string actualCipher = Utilities.SM4Utility.EncryptWithCBC(key: key, iv: iv, plainText: plainText);
|
||||
string actualCipher = Utilities.SM4Utility.EncryptWithCBC(encodingKey: (EncodedString)key, encodingIV: (EncodedString)iv, plainData: plainText)!;
|
||||
string expectedCipher = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg==";
|
||||
|
||||
Assert.Equal(expectedCipher, actualCipher);
|
||||
@@ -24,7 +26,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
|
||||
string cipherText = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg==";
|
||||
|
||||
string actualPlain = Utilities.SM4Utility.DecryptWithCBC(key: key, iv: iv, cipherText: cipherText);
|
||||
string actualPlain = Utilities.SM4Utility.DecryptWithCBC(encodingKey: (EncodedString)key, encodingIV: (EncodedString)iv, encodingCipher: (EncodedString)cipherText)!;
|
||||
string expectedPlain = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!";
|
||||
|
||||
Assert.Equal(expectedPlain, actualPlain);
|
||||
|
||||
Reference in New Issue
Block a user