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

This commit is contained in:
Fu Diwei
2024-02-05 10:53:59 +08:00
committed by RHQYZ
parent 70315cd128
commit 70611189cc
25 changed files with 788 additions and 671 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientEventDecryptionExtensions
{
/// <summary>
@@ -14,7 +15,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
public static WechatTenpayEvent DeserializeEvent(this WechatTenpayClient client, string webhookJson)
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (string.IsNullOrEmpty(webhookJson)) throw new ArgumentNullException(webhookJson);
if (webhookJson is null) throw new ArgumentNullException(webhookJson);
return client.JsonSerializer.Deserialize<WechatTenpayEvent>(webhookJson);
}
@@ -56,11 +57,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
try
{
plainJson = Utilities.AESUtility.DecryptWithGCM(
key: client.Credentials.MerchantV3Secret,
nonce: webhookEventResource.Nonce,
aad: webhookEventResource.AssociatedData,
cipherText: webhookEventResource.CipherText
);
encodingKey: new EncodedString(client.Credentials.MerchantV3Secret, EncodingKinds.Literal),
encodingNonce: new EncodedString(webhookEventResource.Nonce, EncodingKinds.Literal),
encodingAssociatedData: new EncodedString(webhookEventResource.AssociatedData, EncodingKinds.Literal),
encodingCipher: new EncodedString(webhookEventResource.CipherText, EncodingKinds.Base64)
)!;
}
catch (Exception ex)
{
@@ -75,17 +76,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要256bit的前 128bit。
byte[] secretBytes = Utilities.SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
byte[] secretBytes = Utilities.SM3Utility.Hash(EncodedString.FromLiteralString(client.Credentials.MerchantV3Secret));
byte[] keyBytes = new byte[16];
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
byte[] plainBytes = Utilities.SM4Utility.DecryptWithGCM(
keyBytes: keyBytes,
nonceBytes: Encoding.UTF8.GetBytes(webhookEventResource.Nonce),
aadBytes: webhookEventResource.AssociatedData is null ? null : Encoding.UTF8.GetBytes(webhookEventResource.AssociatedData),
cipherBytes: Convert.FromBase64String(webhookEventResource.CipherText)
nonceBytes: EncodedString.FromLiteralString(webhookEventResource.Nonce),
associatedDataBytes: EncodedString.FromLiteralString(webhookEventResource.AssociatedData),
cipherBytes: EncodedString.FromBase64String(webhookEventResource.CipherText)
);
plainJson = Encoding.UTF8.GetString(plainBytes);
plainJson = EncodedString.ToLiteralString(plainBytes).Value!;
}
catch (Exception ex)
{

View File

@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteEcommerceExtensions
{
#region Account
@@ -64,7 +66,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";

View File

@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteMarketingBankExtensions
{
/// <summary>
@@ -26,7 +28,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".txt";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes ?? Array.Empty<byte>())).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes ?? Array.Empty<byte>())).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "text/plain";

View File

@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteMarketingMediaExtensions
{
/// <summary>
@@ -30,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";

View File

@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteMarketingShoppingReceiptExtensions
{
/// <summary>
@@ -25,7 +27,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";

View File

@@ -6,6 +6,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteMerchantMediaExtensions
{
/// <summary>
@@ -27,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";
@@ -56,7 +58,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".mp4";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForVideo(request.FileName!) ?? "video/mp4";

View File

@@ -7,6 +7,8 @@ using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
public static class WechatTenpayClientExecuteMerchantServiceExtensions
{
/// <summary>
@@ -250,7 +252,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SHA256Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
request.FileHash = EncodedString.ToHexString(Utilities.SHA256Utility.Hash(request.FileBytes)).Value!.ToLower();
if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";

View File

@@ -33,10 +33,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string nonce = Guid.NewGuid().ToString("N");
string package = $"prepay_id={prepayId}";
string sign = Utilities.RSAUtility.SignWithSHA256(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
string sign = Utilities.RSAUtility.Sign(
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
message: $"{appId}\n{timestamp}\n{nonce}\n{package}\n"
);
)!;
return new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{
@@ -85,10 +85,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string nonce = Guid.NewGuid().ToString("N");
string sign = Utilities.RSAUtility.SignWithSHA256(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
string sign = Utilities.RSAUtility.Sign(
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
message: $"{appId}\n{timestamp}\n{nonce}\n{prepayId}\n"
);
)!;
return new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{

View File

@@ -82,29 +82,30 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue
);
certificatePem: certificate,
plainData: oldValue,
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
)!;
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = RSAUtility.EncryptWithECBByCertificate(
certificate: certificate,
plainText: oldValue,
paddingMode: "PKCS1PADDING"
);
certificatePem: certificate,
plainData: oldValue,
paddingMode: RSAUtility.PADDING_MODE_PKCS1
)!;
}
break;
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = SM2Utility.EncryptByCertificate(
certificate: certificate,
plainText: oldValue,
certificatePem: certificate,
plainData: oldValue,
asn1Encoding: true
);
)!;
}
break;

View File

@@ -1,10 +1,10 @@
using System;
using System.Linq;
using System.Reflection;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
@@ -37,11 +37,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
certificate.EncryptCertificate.CipherText = AESUtility.DecryptWithGCM(
key: client.Credentials.MerchantV3Secret,
nonce: certificate.EncryptCertificate.Nonce,
aad: certificate.EncryptCertificate.AssociatedData,
cipherText: certificate.EncryptCertificate.CipherText
);
encodingKey: new EncodedString(client.Credentials.MerchantV3Secret, EncodingKinds.Literal),
encodingNonce: new EncodedString(certificate.EncryptCertificate.Nonce, EncodingKinds.Literal),
encodingAssociatedData: new EncodedString(certificate.EncryptCertificate.AssociatedData, EncodingKinds.Literal),
encodingCipher: new EncodedString(certificate.EncryptCertificate.CipherText, EncodingKinds.Base64)
)!;
}
break;
@@ -52,17 +52,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要256bit的前 128bit。
byte[] secretBytes = SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
byte[] secretBytes = SM3Utility.Hash(EncodedString.FromLiteralString(client.Credentials.MerchantV3Secret));
byte[] keyBytes = new byte[16];
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
byte[] plainBytes = SM4Utility.DecryptWithGCM(
keyBytes: keyBytes,
nonceBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.Nonce),
aadBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.AssociatedData ?? string.Empty),
cipherBytes: Convert.FromBase64String(certificate.EncryptCertificate.CipherText)
nonceBytes: EncodedString.FromLiteralString(certificate.EncryptCertificate.Nonce),
associatedDataBytes: EncodedString.FromLiteralString(certificate.EncryptCertificate.AssociatedData),
cipherBytes: EncodedString.FromBase64String(certificate.EncryptCertificate.CipherText)
);
certificate.EncryptCertificate.CipherText = Encoding.UTF8.GetString(plainBytes);
certificate.EncryptCertificate.CipherText = EncodedString.ToLiteralString(plainBytes).Value!;
}
break;
@@ -123,29 +123,30 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
{
newValue = RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue
);
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
)!;
}
break;
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
newValue = RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
paddingMode: "PKCS1PADDING"
);
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
paddingMode: RSAUtility.PADDING_MODE_PKCS1
)!;
}
break;
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
newValue = SM2Utility.Decrypt(
privateKey: client.Credentials.MerchantCertificatePrivateKey,
cipherText: oldValue,
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
asn1Encoding: true
);
)!;
}
break;

View File

@@ -2,6 +2,7 @@ using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Primitives;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal static class WechatTenpayClientSigningExtensions
@@ -36,10 +37,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
error = null;
try
{
return Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
certificate: entry.Value.Certificate,
return Utilities.RSAUtility.VerifyByCertificate(
certificatePem: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
);
}
catch (Exception ex)
@@ -74,9 +75,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
try
{
return Utilities.SM2Utility.VerifyWithSM3ByCertificate(
certificate: entry.Value.Certificate,
certificatePem: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
);
}
catch (Exception ex)

View File

@@ -56,7 +56,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
{
try
{
signText = Utilities.RSAUtility.SignWithSHA256(_mchCertPk, msgText);
signText = Utilities.RSAUtility.Sign(_mchCertPk, msgText).Value!;
}
catch (Exception ex)
{
@@ -69,7 +69,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
{
try
{
signText = Utilities.SM2Utility.SignWithSM3(_mchCertPk, msgText);
signText = Utilities.SM2Utility.SignWithSM3(_mchCertPk, msgText).Value!;
}
catch (Exception ex)
{

View File

@@ -75,16 +75,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
case ALGORITHM_TYPE_RSA:
{
SerialNumber = Utilities.RSAUtility.ExportSerialNumberFromCertificate(certificate).ToUpper();
EffectiveTime = Utilities.RSAUtility.ExportEffectiveTimeFromCertificate(certificate);
ExpireTime = Utilities.RSAUtility.ExportExpireTimeFromCertificate(certificate);
EffectiveTime = Utilities.RSAUtility.ExportValidFromDateFromCertificate(certificate);
ExpireTime = Utilities.RSAUtility.ExportValidToDateFromCertificate(certificate);
}
break;
case ALGORITHM_TYPE_SM2:
{
SerialNumber = Utilities.SM2Utility.ExportSerialNumberFromCertificate(certificate).ToUpper();
EffectiveTime = Utilities.SM2Utility.ExportEffectiveTimeFromCertificate(certificate);
ExpireTime = Utilities.SM2Utility.ExportExpireTimeFromCertificate(certificate);
EffectiveTime = Utilities.SM2Utility.ExportValidFromDateFromCertificate(certificate);
ExpireTime = Utilities.SM2Utility.ExportValidToDateFromCertificate(certificate);
}
break;

View File

@@ -1,29 +1,32 @@
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
using SKIT.FlurlHttpClient.Primitives;
/// <summary>
/// AES 算法工具类。
/// </summary>
public static class AESUtility
{
private const string AES_CIPHER_ALGORITHM_GCM = "AES/GCM";
private const string AES_CIPHER_PADDING_NOPADDING = "NoPadding";
/// <summary>
/// 填充模式:NoPadding
/// </summary>
public const string PADDING_MODE_NOPADDING = "NoPadding";
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="keyBytes">AES 密钥字节数组。</param>
/// <param name="nonceBytes">加密使用的初始化向量字节数组。</param>
/// <param name="aadBytes">加密使用的附加数据字节数组。</param>
/// <param name="nonceBytes">初始化向量字节数组。</param>
/// <param name="associatedDataBytes">附加数据字节数组。</param>
/// <param name="cipherBytes">待解密数据字节数组。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="AES_CIPHER_PADDING_NOPADDING"/></param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/></param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = AES_CIPHER_PADDING_NOPADDING)
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_NOPADDING)
{
const int KEY_LENGTH_BYTE = 32;
const int NONCE_LENGTH_BYTE = 12;
@@ -36,12 +39,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes));
IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", AES_CIPHER_ALGORITHM_GCM, paddingMode));
IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}");
ICipherParameters cipherParams = new AeadParameters(
new KeyParameter(keyBytes),
TAG_LENGTH_BYTE * 8,
nonceBytes,
aadBytes
associatedDataBytes
);
cipher.Init(false, cipherParams);
byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
@@ -53,26 +56,26 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="key">AES 密钥。</param>
/// <param name="nonce">加密使用的初始化向量。</param>
/// <param name="aad">加密使用的附加数据。</param>
/// <param name="cipherText">经 Base64 编码后的待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="AES_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = AES_CIPHER_PADDING_NOPADDING)
/// <param name="encodingKey">经过编码后的(通常为 Base64AES 密钥。</param>
/// <param name="encodingNonce">经过编码后的(通常为 Base64初始化向量。</param>
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64附加数据。</param>
/// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/></param>
/// <returns>解密后的数据。</returns>
public static EncodedString DecryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, EncodedString encodingCipher, string paddingMode = PADDING_MODE_NOPADDING)
{
if (key is null) throw new ArgumentNullException(nameof(key));
if (nonce is null) throw new ArgumentNullException(nameof(nonce));
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
byte[] plainBytes = DecryptWithGCM(
keyBytes: Encoding.UTF8.GetBytes(key),
nonceBytes: Encoding.UTF8.GetBytes(nonce),
aadBytes: aad is not null ? Encoding.UTF8.GetBytes(aad) : null,
cipherBytes: Convert.FromBase64String(cipherText),
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
paddingMode: paddingMode
);
return Encoding.UTF8.GetString(plainBytes);
return EncodedString.ToLiteralString(plainBytes);
}
}
}

View File

@@ -1,72 +1,143 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Asn1.Pkcs;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.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>
/// 填充模式PKCS1Padding。
/// </summary>
public const string PADDING_MODE_PKCS1 = "PKCS1PADDING";
/// <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 X509Certificate ParseCertificatePemToX509(string certificate)
private static X509Certificate ParseCertificatePemToX509(string certificatePem)
{
using (TextReader sreader = new StringReader(certificate))
using (TextReader sreader = new StringReader(certificatePem))
{
PemReader pemReader = new PemReader(sreader);
return (X509Certificate)pemReader.ReadObject();
}
}
private static RsaKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(byte[] privateKeyBytes)
private static RsaKeyParameters ParsePrivateKeyToParameters(byte[] privateKeyBytes)
{
return (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
}
private static RsaKeyParameters ParsePublicKeyPemToPublicKeyParameters(byte[] publicKeyBytes)
private static RsaKeyParameters ParsePublicKeyToParameters(byte[] publicKeyBytes)
{
return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
}
private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes)
private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes, string digestAlgorithm)
{
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
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);
@@ -74,182 +145,187 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingMode)
{
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingMode}");
IBufferedCipher cipher = CipherUtilities.GetCipher($"RSA/ECB/{paddingMode}");
cipher.Init(false, rsaPrivateKeyParams);
return cipher.DoFinal(cipherBytes);
}
private static byte[] EncryptWithECB(RsaKeyParameters rsaPublicKeyParams, byte[] plainBytes, string paddingMode)
{
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingMode}");
IBufferedCipher cipher = CipherUtilities.GetCipher($"RSA/ECB/{paddingMode}");
cipher.Init(true, rsaPublicKeyParams);
return cipher.DoFinal(plainBytes);
}
/// <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 = ParsePrivateKeyPemToPrivateKeyParameters(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 = ParsePublicKeyPemToPublicKeyParameters(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>
/// 使用证书基于 SHA-256 算法验证签名。
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="message">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <param name="certificatePem">证书内容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 VerifyWithSHA256ByCertificate(string certificate, string message, string signature)
public static bool VerifyByCertificate(string certificatePem, string message, EncodedString encodingSignature, string digestAlgorithm = DIGEST_ALGORITHM_SHA256)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
string publicKey = ExportPublicKeyFromCertificate(certificate);
return VerifyWithSHA256(publicKey, message, signature);
string publicKeyPem = ExportPublicKeyFromCertificate(certificatePem);
return Verify(publicKeyPem, message, encodingSignature, digestAlgorithm);
}
/// <summary>
/// 使用私钥基于 ECB 模式解密数据。
/// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="paddingMode">填充模式。(默认值:<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 paddingMode = 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 = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes);
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="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithECB(string privateKey, string cipherText, string paddingMode = 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[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
byte[] cipherBytes = EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64);
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode);
return Encoding.UTF8.GetString(plainBytes);
return EncodedString.ToLiteralString(plainBytes);
}
/// <summary>
/// 使用公钥基于 ECB 模式加密数据。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="paddingMode">填充模式。(默认值:<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 paddingMode = 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 = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes);
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="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param>
/// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptWithECB(string publicKey, string plainText, string paddingMode = 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[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
byte[] plainBytes = EncodedString.FromLiteralString(plainData);
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingMode);
return Convert.ToBase64String(cipherBytes);
return EncodedString.ToBase64String(cipherBytes);
}
/// <summary>
/// 使用证书基于 ECB 模式加密数据。
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="plainText">待加密的文本数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/></param>
/// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptWithECBByCertificate(string certificate, string plainText, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
/// <param name="certificatePem">证书PEM 格式)。</param>
/// <param name="plainData">待加密的数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_OAEPWITHSHA1ANDMGF1"/></param>
/// <returns>经 Base64 编码的加密数据。</returns>
public static EncodedString EncryptWithECBByCertificate(string certificatePem, string plainData, string paddingMode = PADDING_MODE_OAEPWITHSHA1ANDMGF1)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
string publicKey = ExportPublicKeyFromCertificate(certificate);
return EncryptWithECB(publicKey, plainText, paddingMode);
string publicKeyPem = ExportPublicKeyFromCertificate(certificatePem);
return EncryptWithECB(publicKeyPem, plainData, paddingMode);
}
/// <summary>
@@ -259,7 +335,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 转为 -----BEGIN PUBLIC KEY----- ..... -----END PUBLIC KEY-----
/// </para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="certificate">证书内容PEM 格式)。</param>
/// <returns>PKCS#8 公钥PEM 格式)。</returns>
public static string ExportPublicKeyFromCertificate(string certificate)
{
@@ -279,7 +355,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// <para>从 CRT/CER 证书中导出证书序列号。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="certificate">证书内容PEM 格式)。</param>
/// <returns>证书序列号。</returns>
public static string ExportSerialNumberFromCertificate(string certificate)
{
@@ -290,11 +366,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书颁发时间。</para>
/// <para>从 CRT/CER 证书中导出证书有效期的开始时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书颁发时间。</returns>
public static DateTimeOffset ExportEffectiveTimeFromCertificate(string certificate)
/// <param name="certificate">证书内容PEM 格式)。</param>
/// <returns>证书有效期的开始时间。</returns>
public static DateTimeOffset ExportValidFromDateFromCertificate(string certificate)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
@@ -303,11 +379,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书过期时间。</para>
/// <para>从 CRT/CER 证书中导出证书有效期的结束时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书过期时间。</returns>
public static DateTimeOffset ExportExpireTimeFromCertificate(string certificate)
/// <param name="certificate">证书内容PEM 格式)。</param>
/// <returns>证书有效期的结束时间。</returns>
public static DateTimeOffset ExportValidToDateFromCertificate(string certificate)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));

View File

@@ -1,39 +1,44 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.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,13 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
@@ -20,6 +18,8 @@ using Org.BouncyCastle.X509;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
using SKIT.FlurlHttpClient.Primitives;
/// <summary>
/// SM2 算法工具类。
/// <para>此实现遵循国家标准 GM/T 0009-2012 的有关规定。</para>
@@ -33,59 +33,51 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
private const int SM2_C3_LENGTH = 32;
private const int SM2_RS_LENGTH = 32;
private static byte[] ConvertPrivateKeyPkcs8PemToByteArray(string privateKey)
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
{
privateKey = privateKey
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
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 X509Certificate ConvertCertificatePemToX509(string certificate)
private static X509Certificate ConvertCertificatePemToX509(string certificatePem)
{
using (TextReader sreader = new StringReader(certificate))
using (TextReader sreader = new StringReader(certificatePem))
{
PemReader pemReader = new PemReader(sreader);
return (X509Certificate)pemReader.ReadObject();
}
}
private static ECPrivateKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(string privateKey)
{
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey);
return ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes);
}
private static ECPrivateKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(byte[] privateKeyBytes)
private static ECPrivateKeyParameters ParsePrivateKeyToParameters(byte[] privateKeyBytes)
{
return (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
}
private static ECPrivateKeyParameters ParseECPrivateKeyToPrivateKeyParameters(string ecPrivateKeyHex)
private static ECPrivateKeyParameters ParseECPrivateKeyToParameters(byte[] ecPrivateKeyBytes)
{
BigInteger ecPrivateKeyParamsD = new BigInteger(ecPrivateKeyHex, 16);
BigInteger ecPrivateKeyParamsD = new BigInteger(Hex.ToHexString(ecPrivateKeyBytes), 16);
return new ECPrivateKeyParameters(ecPrivateKeyParamsD, SM2_DOMAIN_PARAMS);
}
private static ECPublicKeyParameters ParsePublicKeyPemToPublicKeyParameters(byte[] publicKeyBytes)
private static ECPublicKeyParameters ParsePublicKeyToParameters(byte[] publicKeyBytes)
{
return (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
}
private static ECPublicKeyParameters ParseECPublicKeyToPublicKeyParameters(string ecPublicKeyHex)
private static ECPublicKeyParameters ParseECPublicKeyToParameters(byte[] ecPublicKeyBytes)
{
byte[] ecPublicKeyBytes = Hex.Decode(ecPublicKeyHex);
const int KEY_BYTE_LENGTH = 64;
bool unzipped = ecPublicKeyBytes.FirstOrDefault() == 0x04;
@@ -287,7 +279,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes);
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
return SignWithSM3(sm2PrivateKeyParams, uidBytes, msgBytes, asn1Encoding);
}
@@ -311,27 +303,27 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 使用私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="privateKey">PKCS#8 私钥PEM 格式)。</param>
/// <param name="message">待签名的文本数据。</param>
/// <param name="privateKeyPem">PKCS#8 私钥PEM 格式)。</param>
/// <param name="message">待签名的数据。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>经 Base64 编码的签名。</returns>
public static string SignWithSM3(string privateKey, string message, bool asn1Encoding = true)
/// <returns>经 Base64 编码的签名。</returns>
public static EncodedString SignWithSM3(string privateKeyPem, string message, bool asn1Encoding = true)
{
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[] signBytes = SignWithSM3(
privateKeyBytes: ConvertPrivateKeyPkcs8PemToByteArray(privateKey),
msgBytes: Encoding.UTF8.GetBytes(message),
privateKeyBytes: ConvertPrivateKeyPemToByteArray(privateKeyPem),
msgBytes: EncodedString.FromLiteralString(message),
asn1Encoding: asn1Encoding
);
return Convert.ToBase64String(signBytes);
return EncodedString.ToBase64String(signBytes);
}
/// <summary>
/// 使用 EC 十六进制私钥基于 SM3 算法生成签名。
/// 使用 EC 私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="uidBytes">用户标识符字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
@@ -342,18 +334,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
return SignWithSM3ByECPrivateKey(
ecPrivateKeyHex: Hex.ToHexString(ecPrivateKeyBytes),
uidBytes: uidBytes,
msgBytes: msgBytes,
asn1Encoding: asn1Encoding
);
ECPrivateKeyParameters sm2PrivateKeyParams = ParseECPrivateKeyToParameters(ecPrivateKeyBytes);
return SignWithSM3(sm2PrivateKeyParams, uidBytes, msgBytes, asn1Encoding);
}
/// <summary>
/// 使用 EC 十六进制私钥基于 SM3 算法生成签名。
/// 使用 EC 私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
@@ -368,34 +356,38 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
}
/// <summary>
/// 使用 EC 十六进制私钥基于 SM3 算法生成签名。
/// 使用 EC 私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPrivateKeyHex">EC 私钥(十六进制格式)。</param>
/// <param name="encodingECPrivateKey">经过编码后的通常为十六进制EC 私钥。</param>
/// <param name="uidBytes">用户标识符字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static byte[] SignWithSM3ByECPrivateKey(string ecPrivateKeyHex, byte[] uidBytes, byte[] msgBytes, bool asn1Encoding = true)
public static byte[] SignWithSM3ByECPrivateKey(EncodedString encodingECPrivateKey, byte[] uidBytes, byte[] msgBytes, bool asn1Encoding = true)
{
if (ecPrivateKeyHex is null) throw new ArgumentNullException(nameof(ecPrivateKeyHex));
if (encodingECPrivateKey.Value is null) throw new ArgumentNullException(nameof(encodingECPrivateKey));
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
ECPrivateKeyParameters ecPrivateKeyParams = ParseECPrivateKeyToPrivateKeyParameters(ecPrivateKeyHex);
return SignWithSM3(ecPrivateKeyParams, uidBytes, msgBytes, asn1Encoding);
return SignWithSM3ByECPrivateKey(
ecPrivateKeyBytes: EncodedString.FromString(encodingECPrivateKey, fallbackEncodingKind: EncodingKinds.Hex),
uidBytes: uidBytes,
msgBytes: msgBytes,
asn1Encoding: asn1Encoding
);
}
/// <summary>
/// 使用 EC 十六进制私钥基于 SM3 算法生成签名。
/// 使用 EC 私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPrivateKeyHex">EC 私钥(十六进制格式)。</param>
/// <param name="encodingECPrivateKey">经过编码后的通常为十六进制EC 私钥。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static byte[] SignWithSM3ByECPrivateKey(string ecPrivateKeyHex, byte[] msgBytes, bool asn1Encoding = true)
public static byte[] SignWithSM3ByECPrivateKey(EncodedString encodingECPrivateKey, byte[] msgBytes, bool asn1Encoding = true)
{
return SignWithSM3ByECPrivateKey(
ecPrivateKeyHex: ecPrivateKeyHex,
encodingECPrivateKey: encodingECPrivateKey,
uidBytes: SM2_DEFAULT_UID,
msgBytes: msgBytes,
asn1Encoding: asn1Encoding
@@ -405,10 +397,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="uidBytes">用户标识符字节数组。</param>
/// <param name="msgBytes">待验证的数据字节数。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="msgBytes">待验证的数据字节数。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
@@ -418,16 +410,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes);
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
return VerifyWithSM3(sm2PublicKeyParams, uidBytes, msgBytes, signBytes, asn1Encoding);
}
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="msgBytes">待验证的数据字节数。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="msgBytes">待验证的数据字节数。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
@@ -444,21 +436,21 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKey">PKCS#8 公钥PEM 格式)。</param>
/// <param name="message">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <param name="publicKeyPem">PKCS#8 公钥PEM 格式)。</param>
/// <param name="message">待验证的数据。</param>
/// <param name="encodingSignature">经过编码后的(通常为 Base64签名。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(string publicKey, string message, string signature, bool asn1Encoding = true)
public static bool VerifyWithSM3(string publicKeyPem, string message, EncodedString encodingSignature, bool asn1Encoding = true)
{
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));
return VerifyWithSM3(
publicKeyBytes: ConvertPublicKeyPkcs8PemToByteArray(publicKey),
msgBytes: Encoding.UTF8.GetBytes(message),
signBytes: Convert.FromBase64String(signature),
publicKeyBytes: ConvertPublicKeyPemToByteArray(publicKeyPem),
msgBytes: EncodedString.FromLiteralString(message),
signBytes: EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64),
asn1Encoding: asn1Encoding
);
}
@@ -466,31 +458,31 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 使用证书基于 SM3 算法验证签名。
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="message">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <param name="message">待验证的数据。</param>
/// <param name="encodingSignature">经过编码后的(通常为 Base64签名。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3ByCertificate(string certificate, string message, string signature, bool asn1Encoding = true)
public static bool VerifyWithSM3ByCertificate(string certificatePem, string message, EncodedString encodingSignature, bool asn1Encoding = true)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
string publicKey = ExportPublicKeyFromCertificate(certificate);
string publicKeyPem = ExportPublicKeyFromCertificate(certificatePem);
return VerifyWithSM3(
publicKey: publicKey,
publicKeyPem: publicKeyPem,
message: message,
signature: signature,
encodingSignature: encodingSignature,
asn1Encoding: asn1Encoding
);
}
/// <summary>
/// 使用 EC 十六进制公钥基于 SM3 算法生成签名。
/// 使用 EC 公钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="uidBytes">用户标识符字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="msgBytes">待验证的数据字节数组。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
@@ -499,21 +491,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
return VerifyWithSM3ByECPublicKey(
ecPublicKeyHex: Hex.ToHexString(ecPublicKeyBytes),
uidBytes: uidBytes,
msgBytes: msgBytes,
signBytes: signBytes,
asn1Encoding: asn1Encoding
);
ECPublicKeyParameters sm2PublicKeyParams = ParseECPublicKeyToParameters(ecPublicKeyBytes);
return VerifyWithSM3(sm2PublicKeyParams, uidBytes, msgBytes, signBytes, asn1Encoding);
}
/// <summary>
/// 使用 EC 十六进制公钥基于 SM3 算法生成签名。
/// 使用 EC 公钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="msgBytes">待验证的数据字节数组。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
@@ -528,36 +515,41 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
}
/// <summary>
/// 使用 EC 十六进制公钥基于 SM3 算法生成签名。
/// 使用 EC 公钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPublicKeyHex">EC 公钥(十六进制格式)。</param>
/// <param name="encodingECPublicKey">经过编码后的通常为十六进制EC 公钥。</param>
/// <param name="uidBytes">用户标识符字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="msgBytes">待验证的数据字节数组。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static bool VerifyWithSM3ByECPublicKey(string ecPublicKeyHex, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
public static bool VerifyWithSM3ByECPublicKey(EncodedString encodingECPublicKey, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
{
if (ecPublicKeyHex is null) throw new ArgumentNullException(nameof(ecPublicKeyHex));
if (encodingECPublicKey.Value is null) throw new ArgumentNullException(nameof(encodingECPublicKey));
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
ECPublicKeyParameters ecPublicKeyParams = ParseECPublicKeyToPublicKeyParameters(ecPublicKeyHex);
return VerifyWithSM3(ecPublicKeyParams, uidBytes, msgBytes, signBytes, asn1Encoding);
return VerifyWithSM3ByECPublicKey(
ecPublicKeyBytes: EncodedString.FromString(encodingECPublicKey, fallbackEncodingKind: EncodingKinds.Hex),
uidBytes: uidBytes,
msgBytes: msgBytes,
signBytes: signBytes,
asn1Encoding: asn1Encoding
);
}
/// <summary>
/// 使用 EC 十六进制公钥基于 SM3 算法生成签名。
/// 使用 EC 公钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="ecPublicKeyHex">EC 公钥(十六进制格式)。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <param name="signBytes">待验证的签名字节数。</param>
/// <param name="encodingECPublicKey">经过编码后的通常为十六进制EC 公钥。</param>
/// <param name="msgBytes">待验证的数据字节数组。</param>
/// <param name="signBytes">签名字节数。</param>
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>签名字节数组。</returns>
public static bool VerifyWithSM3ByECPublicKey(string ecPublicKeyHex, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
public static bool VerifyWithSM3ByECPublicKey(EncodedString encodingECPublicKey, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true)
{
return VerifyWithSM3ByECPublicKey(
ecPublicKeyHex: ecPublicKeyHex,
encodingECPublicKey: encodingECPublicKey,
uidBytes: SM2_DEFAULT_UID,
msgBytes: msgBytes,
signBytes: signBytes,
@@ -568,8 +560,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 使用私钥解密数据。
/// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] Decrypt(byte[] privateKeyBytes, byte[] cipherBytes, bool asn1Encoding = true)
@@ -577,68 +569,68 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes);
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
return Decrypt(sm2PrivateKeyParams, cipherBytes, asn1Encoding);
}
/// <summary>
/// 使用私钥解密数据。
/// </summary>
/// <param name="privateKey">PKCS#8 私钥PEM 格式)。</param>
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
/// <param name="privateKeyPem">PKCS#8 私钥PEM 格式)。</param>
/// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>解密后的文本数据。</returns>
public static string Decrypt(string privateKey, string cipherText, bool asn1Encoding = true)
/// <returns>解密后的数据。</returns>
public static EncodedString Decrypt(string privateKeyPem, EncodedString encodingCipher, bool asn1Encoding = true)
{
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[] plainBytes = Decrypt(
privateKeyBytes: ConvertPrivateKeyPkcs8PemToByteArray(privateKey),
cipherBytes: Convert.FromBase64String(cipherText),
privateKeyBytes: ConvertPrivateKeyPemToByteArray(privateKeyPem),
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
asn1Encoding: asn1Encoding
);
return Encoding.UTF8.GetString(plainBytes);
return EncodedString.ToLiteralString(plainBytes);
}
/// <summary>
/// 使用 EC 十六进制私钥解密数据。
/// 使用 EC 私钥解密数据。
/// </summary>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="ecPrivateKeyBytes">EC 私钥字节数。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] cipherBytes, bool asn1Encoding = true)
{
if (ecPrivateKeyBytes is null) throw new ArgumentNullException(nameof(ecPrivateKeyBytes));
ECPrivateKeyParameters ecPrivateKeyParams = ParseECPrivateKeyToParameters(ecPrivateKeyBytes);
return Decrypt(ecPrivateKeyParams, cipherBytes, asn1Encoding);
}
/// <summary>
/// 使用 EC 私钥解密数据。
/// </summary>
/// <param name="encodingECPrivateKey">经过编码后的通常为十六进制EC 私钥。</param>
/// <param name="cipherBytes">待解密的数据字节数组。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>解密后的数据。</returns>
public static byte[] DecryptByECPrivateKey(EncodedString encodingECPrivateKey, byte[] cipherBytes, bool asn1Encoding = true)
{
if (encodingECPrivateKey.Value is null) throw new ArgumentNullException(nameof(encodingECPrivateKey));
return DecryptByECPrivateKey(
ecPrivateKeyHex: Hex.ToHexString(ecPrivateKeyBytes),
ecPrivateKeyBytes: EncodedString.FromString(encodingECPrivateKey, fallbackEncodingKind: EncodingKinds.Hex),
cipherBytes: cipherBytes,
asn1Encoding: asn1Encoding
);
}
/// <summary>
/// 使用 EC 十六进制私钥解密数据。
/// </summary>
/// <param name="ecPrivateKeyHex">EC 私钥(十六进制格式)。</param>
/// <param name="cipherBytes">待解密的数据字节数据。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>解密后的文本数据。</returns>
public static byte[] DecryptByECPrivateKey(string ecPrivateKeyHex, byte[] cipherBytes, bool asn1Encoding = true)
{
if (ecPrivateKeyHex is null) throw new ArgumentNullException(nameof(ecPrivateKeyHex));
ECPrivateKeyParameters ecPrivateKeyParams = ParseECPrivateKeyToPrivateKeyParameters(ecPrivateKeyHex);
return Decrypt(ecPrivateKeyParams, cipherBytes, asn1Encoding);
}
/// <summary>
/// 使用公钥加密数据。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>加密后的数据字节数组。</returns>
public static byte[] Encrypt(byte[] publicKeyBytes, byte[] plainBytes, bool asn1Encoding = true)
@@ -646,77 +638,77 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes);
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
return Encrypt(sm2PublicKeyParams, plainBytes, asn1Encoding);
}
/// <summary>
/// 使用公钥加密数据。
/// </summary>
/// <param name="publicKey">PKCS#8 公钥PEM 格式)。</param>
/// <param name="plainText">待加密的文本数据。</param>
/// <param name="publicKeyPem">PKCS#8 公钥PEM 格式)。</param>
/// <param name="plainData">待加密的数据。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>经 Base64 编码的加密数据。</returns>
public static string Encrypt(string publicKey, string plainText, bool asn1Encoding = true)
/// <returns>经 Base64 编码的加密数据。</returns>
public static EncodedString Encrypt(string publicKeyPem, string plainData, bool asn1Encoding = true)
{
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[] cipherBytes = Encrypt(
publicKeyBytes: ConvertPublicKeyPkcs8PemToByteArray(publicKey),
plainBytes: Encoding.UTF8.GetBytes(plainText),
publicKeyBytes: ConvertPublicKeyPemToByteArray(publicKeyPem),
plainBytes: EncodedString.FromLiteralString(plainData),
asn1Encoding: asn1Encoding
);
return Convert.ToBase64String(cipherBytes);
return EncodedString.ToBase64String(cipherBytes);
}
/// <summary>
/// 使用证书加密数据。
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="plainText">待加密的文本数据。</param>
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <param name="plainData">待加密的数据。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptByCertificate(string certificate, string plainText, bool asn1Encoding = true)
/// <returns>经 Base64 编码的加密数据。</returns>
public static EncodedString EncryptByCertificate(string certificatePem, string plainData, bool asn1Encoding = true)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
return Encrypt(
publicKey: ExportPublicKeyFromCertificate(certificate),
plainText: plainText,
publicKeyPem: ExportPublicKeyFromCertificate(certificatePem),
plainData: plainData,
asn1Encoding: asn1Encoding
);
}
/// <summary>
/// 使用 EC 十六进制公钥加密数据。
/// 使用 EC 公钥加密数据。
/// </summary>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="ecPublicKeyBytes">EC 公钥字节数。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>加密后的数据字节数组。</returns>
public static byte[] EncryptByECPublicKey(byte[] ecPublicKeyBytes, byte[] plainBytes, bool asn1Encoding = true)
{
return EncryptByECPublicKey(
ecPublicKeyHex: Hex.ToHexString(ecPublicKeyBytes),
plainBytes: plainBytes,
asn1Encoding: asn1Encoding
);
ECPublicKeyParameters ecPublicKeyParams = ParseECPublicKeyToParameters(ecPublicKeyBytes);
return Encrypt(ecPublicKeyParams, plainBytes, asn1Encoding);
}
/// <summary>
/// 使用 EC 十六进制公钥加密数据。
/// 使用 EC 公钥加密数据。
/// </summary>
/// <param name="ecPublicKeyHex">EC 公钥(十六进制格式)。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="encodingECPublicKey">EC 公钥(十六进制格式)。</param>
/// <param name="plainBytes">待加密的数据字节数。</param>
/// <param name="asn1Encoding">指示加密结果是否为 ASN.1 编码的形式。默认值true</param>
/// <returns>加密后的数据字节数组。</returns>
public static byte[] EncryptByECPublicKey(string ecPublicKeyHex, byte[] plainBytes, bool asn1Encoding = true)
public static byte[] EncryptByECPublicKey(EncodedString encodingECPublicKey, byte[] plainBytes, bool asn1Encoding = true)
{
if (ecPublicKeyHex is null) throw new ArgumentNullException(nameof(ecPublicKeyHex));
if (encodingECPublicKey.Value is null) throw new ArgumentNullException(nameof(encodingECPublicKey));
ECPublicKeyParameters ecPublicKeyParams = ParseECPublicKeyToPublicKeyParameters(ecPublicKeyHex);
return Encrypt(ecPublicKeyParams, plainBytes, asn1Encoding);
return EncryptByECPublicKey(
ecPublicKeyBytes: EncodedString.FromString(encodingECPublicKey, fallbackEncodingKind: EncodingKinds.Hex),
plainBytes: plainBytes,
asn1Encoding: asn1Encoding
);
}
/// <summary>
@@ -726,15 +718,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 转为 -----BEGIN PUBLIC KEY----- ..... -----END PUBLIC KEY-----
/// </para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <returns>PKCS#8 公钥PEM 格式)。</returns>
public static string ExportPublicKeyFromCertificate(string certificate)
public static string ExportPublicKeyFromCertificate(string certificatePem)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
using (TextWriter swriter = new StringWriter())
{
X509Certificate x509cert = ConvertCertificatePemToX509(certificate);
X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem);
ECPublicKeyParameters exPublicKeyParams = (ECPublicKeyParameters)x509cert.GetPublicKey();
PemWriter pemWriter = new PemWriter(swriter);
pemWriter.WriteObject(exPublicKeyParams);
@@ -746,39 +738,39 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// <para>从 CRT/CER 证书中导出证书序列号。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <returns>证书序列号。</returns>
public static string ExportSerialNumberFromCertificate(string certificate)
public static string ExportSerialNumberFromCertificate(string certificatePem)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
X509Certificate x509cert = ConvertCertificatePemToX509(certificate);
X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem);
return x509cert.SerialNumber.ToString(16);
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书颁发时间。</para>
/// <para>从 CRT/CER 证书中导出证书有效期的开始时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书颁发时间。</returns>
public static DateTimeOffset ExportEffectiveTimeFromCertificate(string certificate)
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <returns>证书有效期的开始时间。</returns>
public static DateTimeOffset ExportValidFromDateFromCertificate(string certificatePem)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
X509Certificate x509cert = ConvertCertificatePemToX509(certificate);
X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem);
return new DateTimeOffset(x509cert.NotBefore);
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书过期时间。</para>
/// <para>从 CRT/CER 证书中导出证书有效期的结束时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书过期时间。</returns>
public static DateTimeOffset ExportExpireTimeFromCertificate(string certificate)
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <returns>证书有效期的结束时间。</returns>
public static DateTimeOffset ExportValidToDateFromCertificate(string certificatePem)
{
if (certificate is null) throw new ArgumentNullException(nameof(certificate));
if (certificatePem is null) throw new ArgumentNullException(nameof(certificatePem));
X509Certificate x509cert = ConvertCertificatePemToX509(certificate);
X509Certificate x509cert = ConvertCertificatePemToX509(certificatePem);
return new DateTimeOffset(x509cert.NotAfter);
}
@@ -789,12 +781,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 转为 04+X|Y 结构的十六进制字符串。
/// </para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="certificatePem">证书内容PEM 格式)。</param>
/// <returns>EC 公钥(十六进制格式)。</returns>
public static string ExportECPublicKeyFromCertificate(string certificate)
public static string ExportECPublicKeyFromCertificate(string certificatePem)
{
string publicKey = ExportPublicKeyFromCertificate(certificate);
return ExportECPublicKeyFromPublicKey(publicKey);
string publicKeyPem = ExportPublicKeyFromCertificate(certificatePem);
return ExportECPublicKeyFromPublicKey(publicKeyPem);
}
/// <summary>
@@ -804,12 +796,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 转为 04+X|Y 结构的 130(128+2) 位的十六进制字符串。
/// </para>
/// </summary>
/// <param name="publicKey">PKCS#8 公钥PEM 格式)。</param>
/// <param name="publicKeyPem">PKCS#8 公钥PEM 格式)。</param>
/// <returns>EC 公钥(十六进制格式)。</returns>
public static string ExportECPublicKeyFromPublicKey(string publicKey)
public static string ExportECPublicKeyFromPublicKey(string publicKeyPem)
{
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey);
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes);
byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
ECPoint ecPublicKeyPoint = sm2PublicKeyParams.Q;
string ecPublicKeyX = ecPublicKeyPoint.XCoord.ToBigInteger().ToString(16);
string ecPublicKeyY = ecPublicKeyPoint.YCoord.ToBigInteger().ToString(16);
@@ -823,11 +815,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 转为 64 位的十六进制字符串。
/// </para>
/// </summary>
/// <param name="privateKey">PKCS#8 私钥PEM 格式)。</param>
/// <param name="privateKeyPem">PKCS#8 私钥PEM 格式)。</param>
/// <returns>EC 私钥(十六进制格式)。</returns>
public static string ExportECPrivateKeyFromPrivateKey(string privateKey)
public static string ExportECPrivateKeyFromPrivateKey(string privateKeyPem)
{
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKey);
byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
return sm2PrivateKeyParams.D.ToString(16);
}
}

View File

@@ -1,19 +1,20 @@
using System;
using System.Text;
using Org.BouncyCastle.Crypto.Digests;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.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.TenpayV3.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,29 +1,32 @@
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
using SKIT.FlurlHttpClient.Primitives;
/// <summary>
/// SM4 算法工具类。
/// </summary>
public static class SM4Utility
{
private const string SM4_CIPHER_ALGORITHM_GCM = "SM4/GCM";
private const string SM4_CIPHER_PADDING_NOPADDING = "NoPadding";
/// <summary>
/// 填充模式:NoPadding
/// </summary>
private const string PADDING_MODE_NOPADDING = "NoPadding";
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="keyBytes">SM4 密钥字节数组。</param>
/// <param name="nonceBytes">加密使用的初始化向量字节数组。</param>
/// <param name="aadBytes">加密使用的附加数据字节数组。</param>
/// <param name="nonceBytes">初始化向量字节数组。</param>
/// <param name="associatedDataBytes">附加数据字节数组。</param>
/// <param name="cipherBytes">待解密数据字节数组。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_NOPADDING"/></param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/></param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_NOPADDING)
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_NOPADDING)
{
const int KEY_LENGTH_BYTE = 16;
const int NONCE_LENGTH_BYTE = 12;
@@ -36,12 +39,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes));
IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", SM4_CIPHER_ALGORITHM_GCM, paddingMode));
IBufferedCipher cipher = CipherUtilities.GetCipher($"SM4/GCM/{paddingMode}");
ICipherParameters cipherParams = new AeadParameters(
new KeyParameter(keyBytes),
TAG_LENGTH_BYTE * 8,
nonceBytes,
aadBytes
associatedDataBytes
);
cipher.Init(false, cipherParams);
byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
@@ -53,26 +56,26 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="key">SM4 密钥。</param>
/// <param name="nonce">加密使用的初始化向量。</param>
/// <param name="aad">加密使用的附加数据。</param>
/// <param name="cipherText">经 Base64 编码后的待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = SM4_CIPHER_PADDING_NOPADDING)
/// <param name="encodingKey">经过编码后的(通常为 Base64SM4 密钥。</param>
/// <param name="encodingNonce">经过编码后的(通常为 Base64初始化向量。</param>
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64附加数据。</param>
/// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/></param>
/// <returns>解密后的数据。</returns>
public static EncodedString DecryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, EncodedString encodingCipher, string paddingMode = PADDING_MODE_NOPADDING)
{
if (key is null) throw new ArgumentNullException(nameof(key));
if (nonce is null) throw new ArgumentNullException(nameof(nonce));
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
byte[] plainBytes = DecryptWithGCM(
keyBytes: Encoding.UTF8.GetBytes(key),
nonceBytes: Encoding.UTF8.GetBytes(nonce),
aadBytes: aad is null ? null : Encoding.UTF8.GetBytes(aad),
cipherBytes: Convert.FromBase64String(cipherText),
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
paddingMode: paddingMode
);
return Encoding.UTF8.GetString(plainBytes);
return EncodedString.ToLiteralString(plainBytes);
}
}
}