refactor(tenpaybusiness): 优化加解密及哈希算法工具类

This commit is contained in:
Fu Diwei
2024-02-05 10:53:54 +08:00
committed by RHQYZ
parent a11df544e5
commit 70315cd128
16 changed files with 576 additions and 388 deletions

View File

@@ -5,6 +5,6 @@
/// <summary>
/// SHA256withRSA。
/// </summary>
public const string SHA245_WITH_RSA = "SHA256withRSA";
public const string SHA256_WITH_RSA = "SHA256withRSA";
}
}

View File

@@ -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
);

View File

@@ -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";

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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>
/// 获取或设置微企付平台账号。

View File

@@ -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)

View File

@@ -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);