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

This commit is contained in:
Fu Diwei
2024-02-07 18:03:28 +08:00
committed by RHQYZ
parent a11df544e5
commit 70315cd128
16 changed files with 576 additions and 388 deletions
@@ -5,6 +5,6 @@
/// <summary> /// <summary>
/// SHA256withRSA。 /// SHA256withRSA。
/// </summary> /// </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, webhookNonce: webhookNonce,
webhookBody: webhookBody, webhookBody: webhookBody,
webhookSignature: webhookSignature, webhookSignature: webhookSignature,
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
webhookSerialNumber: webhookSerialNumber, webhookSerialNumber: webhookSerialNumber,
out _ out _
); );
@@ -112,7 +112,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
webhookNonce: webhookNonce, webhookNonce: webhookNonce,
webhookBody: webhookBody, webhookBody: webhookBody,
webhookSignature: webhookSignature, webhookSignature: webhookSignature,
webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
webhookSerialNumber: webhookSerialNumber, webhookSerialNumber: webhookSerialNumber,
out error out error
); );
@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayBusinessClientExecuteFileUploadsExtensions public static class WechatTenpayBusinessClientExecuteFileUploadsExtensions
{ {
/// <summary> /// <summary>
@@ -25,7 +27,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png"; request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null) 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) if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png"; request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";
@@ -1,9 +1,10 @@
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayBusinessClientRequestEncryptionExtensions public static class WechatTenpayBusinessClientRequestEncryptionExtensions
{ {
/// <summary> /// <summary>
@@ -20,36 +21,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
try try
{ {
bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any(); bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpayBusinessSensitiveAttribute));
if (requireEncrypt) 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) Utilities.ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
request.WechatpayEncryption = new WechatTenpayBusinessRequestEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm };
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.WechatpayEncryption.Algorithm))
{ {
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) => if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpayBusinessSensitivePropertyAttribute)))
{ return (false, oldValue);
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
if (attr is null)
return (false, oldValue);
string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!; string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!;
string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!; string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!;
string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKey: client.Credentials.TBEPCertificatePublicKey, plainText: sm4Key); string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKeyPem: client.Credentials.TBEPCertificatePublicKey, plainData: sm4Key).Value!;
request.WechatpayEncryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber; request.WechatpayEncryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber;
request.WechatpayEncryption.EncryptedKey = sm4EncryptedKey; request.WechatpayEncryption.EncryptedKey = sm4EncryptedKey;
request.WechatpayEncryption.IV = sm4IV; request.WechatpayEncryption.IV = sm4IV;
string newValue = Utilities.SM4Utility.EncryptWithCBC(key: sm4Key, iv: sm4IV, plainText: oldValue); string newValue = Utilities.SM4Utility.EncryptWithCBC(encodingKey: new EncodedString(sm4Key, EncodingKinds.Base64), encodingIV: new EncodedString(sm4IV, EncodingKinds.Base64), plainData: oldValue).Value!;
return (true, newValue); return (true, newValue);
}); });
} }
else else
{ {
throw new WechatTenpayBusinessException($"Failed to encrypt request. Unsupported encryption algorithm: \"{request.WechatpayEncryption.Algorithm}\"."); throw new WechatTenpayBusinessException($"Failed to encrypt request. Unsupported encryption algorithm: \"{request.WechatpayEncryption.Algorithm}\".");
}
} }
} }
catch (WechatTenpayBusinessException) catch (WechatTenpayBusinessException)
@@ -1,9 +1,10 @@
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayBusinessClientResponseDecryptionExtensions public static class WechatTenpayBusinessClientResponseDecryptionExtensions
{ {
/// <summary> /// <summary>
@@ -23,41 +24,40 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
try try
{ {
bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any(); bool requireDecrypt = response.GetType().IsDefined(typeof(WechatTenpayBusinessSensitiveAttribute));
if (requireDecrypt) 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) Utilities.ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) =>
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.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) => if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpayBusinessSensitivePropertyAttribute)))
{ return (false, oldValue);
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
if (attr is null)
return (false, oldValue);
string sm4EncryptedKey = response.WechatpayEncryption.EncryptedKey!; string sm4EncryptedKey = response.WechatpayEncryption.EncryptedKey!;
string sm4Key = Utilities.RSAUtility.DecryptWithECB( string sm4Key = Utilities.RSAUtility.DecryptWithECB(
privateKey: response.WechatpayEncryption.PlatformId is not null ? client.Credentials.PlatformCertificatePrivateKey! : privateKeyPem: response.WechatpayEncryption.PlatformId is not null ? client.Credentials.PlatformCertificatePrivateKey! :
response.WechatpayEncryption.EnterpriseId is not null ? client.Credentials.EnterpriseCertificatePrivateKey! : response.WechatpayEncryption.EnterpriseId is not null ? client.Credentials.EnterpriseCertificatePrivateKey! :
string.Empty, string.Empty,
cipherText: sm4EncryptedKey encodingCipher: new EncodedString(sm4EncryptedKey, EncodingKinds.Base64)
); )!;
string sm4IV = response.WechatpayEncryption.IV!; string sm4IV = response.WechatpayEncryption.IV!;
string newValue = Utilities.SM4Utility.DecryptWithCBC(key: sm4Key, iv: sm4IV, cipherText: oldValue); 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); return (true, newValue);
}); });
} }
else else
{ {
throw new WechatTenpayBusinessException($"Failed to decrypt response. Unsupported encryption algorithm: \"{response.WechatpayEncryption.Algorithm}\"."); throw new WechatTenpayBusinessException($"Failed to decrypt response. Unsupported encryption algorithm: \"{response.WechatpayEncryption.Algorithm}\".");
}
} }
} }
catch (WechatTenpayBusinessException) catch (WechatTenpayBusinessException)
@@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Text; using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
@@ -65,7 +64,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
responseNonce: responseNonce, responseNonce: responseNonce,
responseBody: responseBody, responseBody: responseBody,
responseSignature: responseSignature, responseSignature: responseSignature,
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, responseSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
responseSerialNumber: responseSerialNumber, responseSerialNumber: responseSerialNumber,
out _ out _
); );
@@ -115,7 +114,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
responseNonce: responseNonce, responseNonce: responseNonce,
responseBody: responseBody, responseBody: responseBody,
responseSignature: responseSignature, responseSignature: responseSignature,
responseSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, responseSignatureAlgorithm: Constants.SignAlgorithms.SHA256_WITH_RSA,
responseSerialNumber: responseSerialNumber, responseSerialNumber: responseSerialNumber,
out error out error
); );
@@ -4,6 +4,8 @@ using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
using SKIT.FlurlHttpClient.Primitives;
internal static class WechatTenpayBusinessClientSigningExtensions internal static class WechatTenpayBusinessClientSigningExtensions
{ {
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strContent, out Exception? error) 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) switch (strSignatureAlgorithm)
{ {
case Constants.SignAlgorithms.SHA245_WITH_RSA: case Constants.SignAlgorithms.SHA256_WITH_RSA:
{ {
if (client.Credentials.TBEPCertificateSerialNumber is not null && if (client.Credentials.TBEPCertificateSerialNumber is not null &&
client.Credentials.TBEPCertificatePublicKey is not null) client.Credentials.TBEPCertificatePublicKey is not null)
@@ -67,10 +69,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
} }
error = null; error = null;
return Utilities.RSAUtility.VerifyWithSHA256( return Utilities.RSAUtility.Verify(
publicKey: client.Credentials.TBEPCertificatePublicKey, publicKeyPem: client.Credentials.TBEPCertificatePublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
); );
} }
catch (Exception ex) catch (Exception ex)
@@ -7,6 +7,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
{ {
using SKIT.FlurlHttpClient.Primitives;
internal class WechatTenpayBusinessRequestSigningInterceptor : HttpInterceptor internal class WechatTenpayBusinessRequestSigningInterceptor : HttpInterceptor
{ {
private const string HTTP_HEADER_PLATFORM_AUTHORIZATION = HttpHeaders.Authorization; private const string HTTP_HEADER_PLATFORM_AUTHORIZATION = HttpHeaders.Authorization;
@@ -64,17 +66,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
switch (_signAlg) switch (_signAlg)
{ {
case Constants.SignAlgorithms.SHA245_WITH_RSA: case Constants.SignAlgorithms.SHA256_WITH_RSA:
{ {
try try
{ {
signText = Utilities.RSAUtility.SignWithSHA256(_platformCertPk, plainText); signText = Utilities.RSAUtility.Sign(_platformCertPk, plainText).Value!;
if (softSignRequired) if (softSignRequired)
{ {
byte[] keyBytes = Convert.FromBase64String(_enterpriseCertPk!); byte[] keyBytes = EncodedString.FromBase64String(_enterpriseCertPk!);
byte[] msgBytes = Convert.FromBase64String(signText); byte[] msgBytes = EncodedString.FromBase64String(signText);
softSignText = Convert.ToBase64String(Utilities.RSAUtility.SignWithSHA256(keyBytes, msgBytes)); softSignText = EncodedString.ToBase64String(Utilities.RSAUtility.Sign(keyBytes, msgBytes)).Value!;
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -1,203 +1,284 @@
using System; using System;
using System.Text; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security; using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
{ {
using SKIT.FlurlHttpClient.Primitives;
/// <summary> /// <summary>
/// RSA 算法工具类。 /// RSA 算法工具类。
/// </summary> /// </summary>
public static class RSAUtility public static class RSAUtility
{ {
private const string RSA_CIPHER_ALGORITHM_ECB = "RSA/ECB"; /// <summary>
private const string RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1 = "OAEPWITHSHA1ANDMGF1PADDING"; /// 填充模式:OAEPwithSHA-256andMGF1Padding。
private const string RSA_SIGNER_ALGORITHM_SHA256 = "SHA-256withRSA"; /// </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("-----BEGIN PRIVATE KEY-----", string.Empty)
.Replace("-----END PRIVATE KEY-----", string.Empty); .Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = Regex.Replace(privateKey, "\\s+", string.Empty); privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
return Convert.FromBase64String(privateKey); 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("-----BEGIN PUBLIC KEY-----", string.Empty)
.Replace("-----END PUBLIC KEY-----", string.Empty); .Replace("-----END PUBLIC KEY-----", string.Empty);
publicKey = Regex.Replace(publicKey, "\\s+", string.Empty); publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty);
return Convert.FromBase64String(publicKey); 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.Init(true, rsaPrivateKeyParams);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length); signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.GenerateSignature(); 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.Init(false, rsaPublicKeyParams);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length); signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(signBytes); 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); cipher.Init(false, rsaPrivateKeyParams);
return cipher.DoFinal(cipherBytes); 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); cipher.Init(true, rsaPublicKeyParams);
return cipher.DoFinal(msgBytes); return cipher.DoFinal(msgBytes);
} }
/// <summary> /// <summary>
/// 使用私钥基于 SHA-256 算法生成签名。 /// 使用私钥生成签名。
/// </summary> /// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param> /// <param name="privateKeyBytes">PKCS#1/PKCS#8 私钥字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param> /// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/></param>
/// <returns>签名字节数组。</returns> /// <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 (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes)); if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
return SignWithSHA256(rsaPrivateKeyParams, msgBytes); return Sign(rsaPrivateKeyParams, msgBytes, digestAlgorithm);
} }
/// <summary> /// <summary>
/// 使用私钥基于 SHA-256 算法生成签名。 /// 使用私钥生成签名。
/// </summary> /// </summary>
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param> /// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥(PEM 格式)。</param>
/// <param name="message">待签名的文本数据。</param> /// <param name="message">待签名的数据。</param>
/// <returns>经 Base64 编码的签名。</returns> /// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/></param>
public static string SignWithSHA256(string privateKey, string message) /// <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)); if (message is null) throw new ArgumentNullException(nameof(message));
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = EncodedString.FromLiteralString(message);
byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes); byte[] signBytes = Sign(privateKeyBytes, msgBytes, digestAlgorithm);
return Convert.ToBase64String(signBytes); return EncodedString.ToBase64String(signBytes);
} }
/// <summary> /// <summary>
/// 使用公钥基于 SHA-256 算法验证签名。 /// 使用公钥验证签名。
/// </summary> /// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param> /// <param name="publicKeyBytes">PKCS#1/PKCS#8 公钥字节数。</param>
/// <param name="msgBytes">待验证的数据字节数。</param> /// <param name="msgBytes">待验证的数据字节数。</param>
/// <param name="signBytes">待验证的签名字节数。</param> /// <param name="signBytes">签名字节数。</param>
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/></param>
/// <returns>验证结果。</returns> /// <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 (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes)); if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
if (signBytes is null) throw new ArgumentNullException(nameof(signBytes)); if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes); return Verify(rsaPublicKeyParams, msgBytes, signBytes, digestAlgorithm);
} }
/// <summary> /// <summary>
/// 使用公钥基于 SHA-256 算法验证签名。 /// 使用公钥验证签名。
/// </summary> /// </summary>
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param> /// <param name="publicKeyPem">PKCS#1/PKCS#8 公钥(PEM 格式)。</param>
/// <param name="message">待验证的文本数据。</param> /// <param name="message">待验证的数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param> /// <param name="encodingSignature">经过编码后的(通常为 Base64签名。</param>
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/></param>
/// <returns>验证结果。</returns> /// <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 (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[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = EncodedString.FromLiteralString(message);
byte[] signBytes = Convert.FromBase64String(signature); byte[] signBytes = EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64);
return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes); return Verify(publicKeyBytes, msgBytes, signBytes, digestAlgorithm);
} }
/// <summary> /// <summary>
/// 使用私钥基于 ECB 模式解密数据。 /// 使用私钥基于 ECB 模式解密数据。
/// </summary> /// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数。</param> /// <param name="privateKeyBytes">PKCS#1/PKCS#8 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param> /// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/></param>
/// <returns>解密后的数据字节数组。</returns> /// <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 (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes)); if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingAlgorithm); return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode);
} }
/// <summary> /// <summary>
/// 使用私钥基于 ECB 模式解密数据。 /// 使用私钥基于 ECB 模式解密数据。
/// </summary> /// </summary>
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param> /// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥(PEM 格式)。</param>
/// <param name="cipherText">经 Base64 编码的待解密数据。</param> /// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/></param>
/// <returns>解密后的文本数据。</returns> /// <returns>解密后的数据。</returns>
public static string DecryptWithECB(string privateKey, string cipherText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static EncodedString DecryptWithECB(string privateKeyPem, EncodedString encodingCipher, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
{ {
if (privateKey is null) throw new ArgumentNullException(nameof(privateKey)); if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem));
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText)); if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
byte[] cipherBytes = Convert.FromBase64String(cipherText); byte[] cipherBytes = EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64);
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingAlgorithm); byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode);
return Encoding.UTF8.GetString(plainBytes); return EncodedString.ToLiteralString(plainBytes);
} }
/// <summary> /// <summary>
/// 使用公钥基于 ECB 模式加密数据。 /// 使用公钥基于 ECB 模式加密数据。
/// </summary> /// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param> /// <param name="publicKeyBytes">PKCS#1/PKCS#8 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param> /// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/></param>
/// <returns>加密后的数据字节数组。</returns> /// <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 (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes)); if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingAlgorithm); return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingMode);
} }
/// <summary> /// <summary>
/// 使用公钥基于 ECB 模式加密数据。 /// 使用公钥基于 ECB 模式加密数据。
/// </summary> /// </summary>
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param> /// <param name="publicKeyPem">PKCS#1/PKCS#8 公钥(PEM 格式)。</param>
/// <param name="plainText">待加密的文本数据。</param> /// <param name="plainData">待加密数据。</param>
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/></param>
/// <returns>经 Base64 编码的加密数据。</returns> /// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptWithECB(string publicKey, string plainText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static EncodedString EncryptWithECB(string publicKeyPem, string plainData, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
{ {
if (publicKey is null) throw new ArgumentNullException(nameof(publicKey)); if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem));
if (plainText is null) throw new ArgumentNullException(nameof(plainText)); if (plainData is null) throw new ArgumentNullException(nameof(plainData));
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); byte[] plainBytes = EncodedString.FromLiteralString(plainData);
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingAlgorithm); byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingMode);
return Convert.ToBase64String(cipherBytes); return EncodedString.ToBase64String(cipherBytes);
} }
} }
} }
@@ -1,39 +1,44 @@
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
{ {
using SKIT.FlurlHttpClient.Primitives;
/// <summary> /// <summary>
/// SHA-256 算法工具类。 /// SHA-256 算法工具类。
/// </summary> /// </summary>
public static class SHA256Utility public static class SHA256Utility
{ {
/// <summary> /// <summary>
/// 获取 SHA-256 信息摘要 /// 计算 SHA-256 哈希值
/// </summary> /// </summary>
/// <param name="bytes">信息字节数组。</param> /// <param name="bytes">要计算哈希值的信息字节数组。</param>
/// <returns>信息摘要字节数组。</returns> /// <returns>哈希值字节数组。</returns>
public static byte[] Hash(byte[] bytes) public static byte[] Hash(byte[] bytes)
{ {
if (bytes is null) throw new ArgumentNullException(nameof(bytes)); if (bytes is null) throw new ArgumentNullException(nameof(bytes));
using SHA256 sha = SHA256.Create(); #if NET5_0_OR_GREATER
return sha.ComputeHash(bytes); return SHA256.HashData(bytes);
#else
using SHA256 sha256 = SHA256.Create();
return sha256.ComputeHash(bytes);
#endif
} }
/// <summary> /// <summary>
/// 获取 SHA-256 信息摘要 /// 计算 SHA-256 哈希值
/// </summary> /// </summary>
/// <param name="message">文本信息。</param> /// <param name="message">要计算哈希值的信息。</param>
/// <returns>信息摘要。</returns> /// <returns>经过十六进制编码的哈希值。</returns>
public static string Hash(string message) public static EncodedString Hash(string message)
{ {
if (message is null) throw new ArgumentNullException(nameof(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); byte[] hashBytes = Hash(msgBytes);
return BitConverter.ToString(hashBytes).Replace("-", string.Empty); return EncodedString.ToHexString(hashBytes);
} }
} }
} }
@@ -1,19 +1,20 @@
using System; using System;
using System.Text;
using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Digests;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
{ {
using SKIT.FlurlHttpClient.Primitives;
/// <summary> /// <summary>
/// SM3 算法工具类。 /// SM3 算法工具类。
/// </summary> /// </summary>
public static class SM3Utility public static class SM3Utility
{ {
/// <summary> /// <summary>
/// 获取 SM3 哈希值。 /// 计算 SM3 哈希值。
/// </summary> /// </summary>
/// <param name="bytes">信息字节数组。</param> /// <param name="bytes">要计算哈希值的信息字节数组。</param>
/// <returns>哈希字节数组。</returns> /// <returns>哈希字节数组。</returns>
public static byte[] Hash(byte[] bytes) public static byte[] Hash(byte[] bytes)
{ {
if (bytes is null) throw new ArgumentNullException(nameof(bytes)); if (bytes is null) throw new ArgumentNullException(nameof(bytes));
@@ -26,17 +27,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
} }
/// <summary> /// <summary>
/// 获取 SM3 哈希值。 /// 计算 SM3 哈希值。
/// </summary> /// </summary>
/// <param name="message">文本信息。</param> /// <param name="message">要计算哈希值的信息。</param>
/// <returns>哈希值。</returns> /// <returns>经过十六进制编码的哈希值。</returns>
public static string Hash(string message) public static EncodedString Hash(string message)
{ {
if (message is null) throw new ArgumentNullException(nameof(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); byte[] hashBytes = Hash(msgBytes);
return BitConverter.ToString(hashBytes).Replace("-", string.Empty); return EncodedString.ToHexString(hashBytes);
} }
} }
} }
@@ -1,86 +1,51 @@
using System; using System;
using System.Text;
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security; using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
{ {
using SKIT.FlurlHttpClient.Primitives;
/// <summary> /// <summary>
/// SM4 算法工具类。 /// SM4 算法工具类。
/// </summary> /// </summary>
public static class SM4Utility public static class SM4Utility
{ {
private const string SM4_ALGORITHM_NAME = "SM4"; /// <summary>
private const string SM4_CIPHER_ALGORITHM_CBC = "SM4/CBC"; /// 填充模式:PKCS7Padding。
private const string SM4_CIPHER_PADDING_PKCS7PADDING = "PKCS7PADDING"; /// </summary>
public const string PADDING_MODE_PKCS7 = "PKCS7PADDING";
private static byte[] DecryptWithCBC(ICipherParameters sm4KeyParams, byte[] cipherBytes, string paddingMode) 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); cipher.Init(false, sm4KeyParams);
return cipher.DoFinal(cipherBytes); return cipher.DoFinal(cipherBytes);
} }
private static byte[] EncryptWithCBC(ICipherParameters sm4KeyParams, byte[] plainBytes, string paddingMode) 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); cipher.Init(true, sm4KeyParams);
return cipher.DoFinal(plainBytes); 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> /// <summary>
/// 基于 CBC 模式解密数据。 /// 基于 CBC 模式解密数据。
/// </summary> /// </summary>
/// <param name="keyBytes">密钥字节数。</param> /// <param name="keyBytes">密钥字节数。</param>
/// <param name="ivBytes">偏移量字节数。</param> /// <param name="ivBytes">偏移量字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param> /// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/></param>
/// <returns>解密后的数据字节数组。</returns> /// <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 (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)); 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); ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
return DecryptWithCBC(sm4keyParamsWithIv, cipherBytes, paddingMode); return DecryptWithCBC(sm4keyParamsWithIv, cipherBytes, paddingMode);
} }
@@ -88,21 +53,62 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <summary> /// <summary>
/// 基于 CBC 模式解密数据。 /// 基于 CBC 模式解密数据。
/// </summary> /// </summary>
/// <param name="key">经 Base64 编码的密钥。</param> /// <param name="encodingKey">经过编码后的(通常为 Base64密钥。</param>
/// <param name="iv">>经 Base64 编码的偏移量。</param> /// <param name="encodingIV">>经过编码后的(通常为 Base64偏移量。</param>
/// <param name="cipherText">经 Base64 编码的待解密数据。</param> /// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/></param> /// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS7"/></param>
/// <returns>解密后的文本数据。</returns> /// <returns>解密后的数据。</returns>
public static string DecryptWithCBC(string key, string iv, string cipherText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING) 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 (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText)); 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[] keyBytes = EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64);
byte[] ivBytes = Convert.FromBase64String(iv); byte[] ivBytes = EncodedString.FromString(encodingIV, fallbackEncodingKind: EncodingKinds.Base64);
byte[] cipherBytes = Convert.FromBase64String(cipherText); byte[] cipherBytes = EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64);
byte[] plainBytes = DecryptWithCBC(keyBytes, ivBytes, cipherBytes, paddingMode); 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;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities 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 ReplaceObjectStringProperties(object targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement)
public static void ReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
{ {
InnerReplacePropertyStringValue(ref obj, replacement); if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
}
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
{
if (obj is null) throw new ArgumentNullException(nameof(obj));
if (replacement is null) throw new ArgumentNullException(nameof(replacement)); if (replacement is null) throw new ArgumentNullException(nameof(replacement));
Type objType = obj.GetType(); ISet<object> visited = new HashSet<object>(ReferenceEqualityComparer.Instance); // 处理循环引用问题
if (!objType.IsClass) InnerReplaceObjectStringProperties(ref targetObj, null, replacement, visited);
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);
}
}
}
} }
}
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) if (currentObj is null) throw new ArgumentNullException(nameof(currentObj));
{ if (replacement is null) throw new ArgumentNullException(nameof(replacement));
var array = (obj as Array)!;
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) if (element is null)
continue; continue;
Type elementType = element.GetType(); Type elementType = element.GetType();
if (elementType == typeof(string)) if (elementType == typeof(string))
{ {
if (currentProp is null) if (currentObjAsArray.IsReadOnly)
continue; continue;
if (!currentProp.CanWrite) if ((string)element == string.Empty)
continue; continue;
var oldValue = (string)element!; var res = replacement(currentObjAsArray, currentProp, (string)element);
var resHandler = replacement(obj!, currentProp, oldValue); if (res.IsModified)
if (resHandler.Modified && !array.IsReadOnly) currentObjAsArray.SetValue(res.NewValue, i);
{
array.SetValue(resHandler.NewValue, i);
}
}
else if (elementType.IsClass)
{
InnerReplacePropertyStringValue(ref element, replacement);
//if (!array.IsReadOnly)
//{
// array.SetValue(element, i);
//}
} }
else 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) if (element is null)
continue; continue;
Type elementType = element.GetType(); Type elementType = element.GetType();
if (elementType == typeof(string)) if (elementType == typeof(string))
{ {
if (currentProp is null) if (currentObjAsList.IsReadOnly)
continue; continue;
if (!currentProp.CanWrite) if ((string)element == string.Empty)
continue; continue;
var oldValue = (string)element!; var res = replacement(currentObjAsList, currentProp, (string)element);
var resHandler = replacement(obj, currentProp, oldValue); if (res.IsModified)
if (resHandler.Modified && !list.IsReadOnly) currentObjAsList[i] = res.NewValue;
{
list[i] = resHandler.NewValue;
}
}
else if (elementType.IsClass)
{
InnerReplacePropertyStringValue(ref element, replacement);
//if (!list.IsReadOnly)
//{
// list[i] = element;
//}
} }
else 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; object? entryValue = entry.Value;
if (entryValue is null) if (entryValue is null)
@@ -160,32 +125,137 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
Type entryValueType = entryValue.GetType(); Type entryValueType = entryValue.GetType();
if (entryValueType == typeof(string)) if (entryValueType == typeof(string))
{ {
if (currentProp is null) if (currentObjAsDictionary.IsReadOnly)
continue; continue;
if (!currentProp.CanWrite) if ((string)entryValue == string.Empty)
continue; continue;
string oldValue = (string)entryValue!; var res = replacement(currentObjAsDictionary, currentProp, (string)entryValue);
var resHandler = replacement(obj, currentProp, oldValue); if (res.IsModified)
if (resHandler.Modified && !dict.IsReadOnly) currentObjAsDictionary[entry.Key] = res.NewValue;
{
dict[entry.Key] = resHandler.NewValue;
}
}
else if (entryValueType.IsClass)
{
InnerReplacePropertyStringValue(ref entryValue, replacement);
//if (!dict.IsReadOnly)
//{
// dict[entry.Key] = entryValue;
//}
} }
else 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> /// <summary>
/// 获取或设置微企付 API 签名认证方式。 /// 获取或设置微企付 API 签名认证方式。
/// <para> /// <para>
/// 默认值:<see cref="Constants.SignAlgorithms.SHA245_WITH_RSA"/> /// 默认值:<see cref="Constants.SignAlgorithms.SHA256_WITH_RSA"/>
/// </para> /// </para>
/// </summary> /// </summary>
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA245_WITH_RSA; public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA256_WITH_RSA;
/// <summary> /// <summary>
/// 获取或设置微企付平台账号。 /// 获取或设置微企付平台账号。
@@ -22,25 +22,43 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem) private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
{ {
using (TextReader textReader = new StringReader(privateKeyPem)) if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----"))
using (PemReader pemReader = new PemReader(textReader))
{ {
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject(); using (TextReader textReader = new StringReader(privateKeyPem))
using (TextWriter textWriter = new StringWriter()) using (PemReader pemReader = new PemReader(textReader))
using (PemWriter pemWriter = new PemWriter(textWriter))
{ {
Pkcs8Generator pkcs8 = new Pkcs8Generator(cipherKeyPair.Private); object pemObject = pemReader.ReadObject();
pemWriter.WriteObject(pkcs8);
pemWriter.Writer.Close();
privateKeyPem = textWriter.ToString()!; if (pemObject is AsymmetricCipherKeyPair)
privateKeyPem = privateKeyPem {
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty) // PKCS#1 格式
.Replace("-----END PRIVATE KEY-----", string.Empty); AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemObject;
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty); using (TextWriter textWriter = new StringWriter())
return Convert.FromBase64String(privateKeyPem); 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) private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingMode)
@@ -2,6 +2,8 @@ using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
{ {
using SKIT.FlurlHttpClient.Primitives;
public class TestCase_ToolsSM4UtilityTests public class TestCase_ToolsSM4UtilityTests
{ {
[Fact(DisplayName = "测试用例:SM4 加密")] [Fact(DisplayName = "测试用例:SM4 加密")]
@@ -11,7 +13,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
string iv = "OGE2YzRkZGQ4YTZjNGRkZA=="; string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
string plainText = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!"; 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=="; string expectedCipher = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg==";
Assert.Equal(expectedCipher, actualCipher); Assert.Equal(expectedCipher, actualCipher);
@@ -24,7 +26,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
string iv = "OGE2YzRkZGQ4YTZjNGRkZA=="; string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
string cipherText = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg=="; 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!"; string expectedPlain = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!";
Assert.Equal(expectedPlain, actualPlain); Assert.Equal(expectedPlain, actualPlain);