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

This commit is contained in:
Fu Diwei 2024-02-04 22:53:47 +08:00 committed by RHQYZ
parent 69955a1f2b
commit a11df544e5
8 changed files with 86 additions and 75 deletions

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
namespace SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance
{
using SKIT.FlurlHttpClient.Primitives;
using SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance.InteropServices;
/// <summary>
@ -200,9 +201,9 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance
throw new WechatWorkFinanceException($"Failed to decrypt random key of the encrypted chat data, because there is no private key matched the verion: \"{request.PublicKeyVersion}\".");
encryptKey = Utilities.RSAUtility.DecryptWithECB(
privateKey: encryptionKeyEntry.Value.PrivateKey,
cipherText: request.EncryptedRandomKey
);
privateKeyPem: encryptionKeyEntry.Value.PrivateKey,
encodingCipher: new EncodedString(request.EncryptedRandomKey, EncodingKinds.Base64)
)!;
}
catch (WechatWorkFinanceException)
{

View File

@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string nonce = Guid.NewGuid().ToString("N");
string sign = Utilities.SHA1Utility.Hash($"jsapi_ticket={jsapiTicket}&noncestr={nonce}&timestamp={timestamp}&url={url.Split('#')[0]}").ToLower();
string sign = Utilities.SHA1Utility.Hash($"jsapi_ticket={jsapiTicket}&noncestr={nonce}&timestamp={timestamp}&url={url.Split('#')[0]}").Value!.ToLower();
return new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{
@ -58,7 +58,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string nonce = Guid.NewGuid().ToString("N");
string sign = Utilities.SHA1Utility.Hash($"jsapi_ticket={jsapiTicket}&noncestr={nonce}&timestamp={timestamp}&url={url.Split('#')[0]}").ToLower();
string sign = Utilities.SHA1Utility.Hash($"jsapi_ticket={jsapiTicket}&noncestr={nonce}&timestamp={timestamp}&url={url.Split('#')[0]}").Value!.ToLower();
return new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()
{

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.Work.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.Work.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.Work.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,6 +1,5 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
@ -9,17 +8,21 @@ using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
{
using SKIT.FlurlHttpClient.Primitives;
/// <summary>
/// RSA 算法工具类。
/// </summary>
public static class RSAUtility
{
private const string RSA_CIPHER_ALGORITHM_NONE = "RSA/ECB";
private const string RSA_CIPHER_PADDING_PKCS1 = "PKCS1PADDING";
/// <summary>
/// 填充模式PKCS1Padding。
/// </summary>
public const string PADDING_MODE_PKCS1 = "PKCS1PADDING";
private static byte[] ConvertPrivateKeyPkcs1PemToByteArray(string privateKey)
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
{
using (TextReader textReader = new StringReader(privateKey))
using (TextReader textReader = new StringReader(privateKeyPem))
using (PemReader pemReader = new PemReader(textReader))
{
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
@ -30,24 +33,19 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
pemWriter.WriteObject(pkcs8);
pemWriter.Writer.Close();
privateKey = textWriter.ToString()!;
privateKey = privateKey
privateKeyPem = textWriter.ToString()!;
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 RsaKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(byte[] privateKeyBytes)
{
return (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
}
private static byte[] DecryptWithECB(RsaKeyParameters rsaPrivateKeyParams, byte[] cipherBytes, string paddingMode)
{
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_NONE}/{paddingMode}");
IBufferedCipher cipher = CipherUtilities.GetCipher($"RSA/ECB/{paddingMode}");
cipher.Init(false, rsaPrivateKeyParams);
return cipher.DoFinal(cipherBytes);
}
@ -55,35 +53,35 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
/// <summary>
/// 使用私钥基于 ECB 模式解密数据。
/// </summary>
/// <param name="privateKeyBytes">PKCS#1 私钥字节数据。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_PKCS1"/></param>
/// <param name="privateKeyBytes">PKCS#1/PKCS#8 私钥字节数组。</param>
/// <param name="cipherBytes">待解密的数据字节数。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS1"/></param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingMode = RSA_CIPHER_PADDING_PKCS1)
public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_PKCS1)
{
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes);
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode);
}
/// <summary>
/// 使用私钥基于 ECB 模式解密数据。
/// </summary>
/// <param name="privateKey">PKCS#1 私钥PEM 格式)。</param>
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_PKCS1"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithECB(string privateKey, string cipherText, string paddingMode = RSA_CIPHER_PADDING_PKCS1)
/// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥PEM 格式)。</param>
/// <param name="encodingCipher">经过编码后的(通常为 Base64待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_PKCS1"/></param>
/// <returns>解密后的数据。</returns>
public static EncodedString DecryptWithECB(string privateKeyPem, EncodedString encodingCipher, string paddingMode = PADDING_MODE_PKCS1)
{
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 = ConvertPrivateKeyPkcs1PemToByteArray(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);
}
}
}

View File

@ -1,39 +1,44 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
{
using SKIT.FlurlHttpClient.Primitives;
/// <summary>
/// SHA-1 算法工具类。
/// </summary>
public static class SHA1Utility
{
/// <summary>
/// 获取 SHA-1 信息摘要
/// 计算 SHA-1 哈希值
/// </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 SHA1 sha = SHA1.Create();
return sha.ComputeHash(bytes);
#if NET5_0_OR_GREATER
return SHA1.HashData(bytes);
#else
using SHA1 sha1 = SHA1.Create();
return sha1.ComputeHash(bytes);
#endif
}
/// <summary>
/// 获取 SHA-1 信息摘要。
/// 计算 SHA-1 哈希值
/// </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

@ -233,7 +233,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
tmp.Sort(StringComparer.Ordinal);
string rawText = string.Join(string.Empty, tmp);
string signText = SHA1Utility.Hash(rawText);
string signText = SHA1Utility.Hash(rawText).Value!;
return signText.ToLower();
}

View File

@ -2,6 +2,8 @@ using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
{
using SKIT.FlurlHttpClient.Primitives;
public class TestCase_ToolsAESUtilityTests
{
[Fact(DisplayName = "测试用例AES-GCM 解密")]
@ -9,10 +11,10 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
{
string key = "f09b03a7a1902b5b4913856f1fd07ab1";
string nonce = "aae8c2e79c5b";
string aad = "certificate";
string associatedData = "certificate";
string cipherText = "x9kkL5w1JuaypcjhrYIP+kVNlN8o8uN4yJyJjy5lg+PyPnQL2Zn//ORaXAyzdaK/WBMVd3u/Y9hLaTBLMyRXzowsrkJ5PT37johye48N7BAJQ0PJwW++d1RdhOOPjoqfmws6rSV5Gv2qhfdKjmpxVjr8xr71dtBt8J2wu+bAV99HHQoAynm/Pp9OQYZgpOQ+1cyFHd43TAxOoFfmixKrXr3HP8lJot0XCUSq4qkr1Hs44FV2KuzntSk8eqKr5N17UcuPF3VYnnnF/AvQ7HuLKWwrHhUbaXfkwy0Q2n36UJMfBj7344S97E8BnS89ojgOPQi+olBPyNgrtDWHgsJAKu7HA6PV/FgmXcrZirje/AH1u25es4z5xItHscm//6rDvALgf7greV5OJzMsSl/KVDtbkjDSzim0j4ZTduIfzh7l6jfOz115ITcNILT9ef2KkcMyBBc89GZlMGeHTbgsBHzGeLawX3dXFjqt5aMnHM3VWCovA2aUM4c//rqkZGf+Va86OEFoJQLiSTFpkOrKxcIxcrbKPLTgiDWRT3wzmnUDg7kSPbluzt3ROvMFq9lB8bO/pBd7TD2w87sfUdKLj69FniX6s37SBeRVhw8GSIvBf5lpLUhqL5zKYlbuAWePuz0wVV4NTtkaVKZlmm8KTODyZDFpsyKPubDDcwT1ftRf5aSVM4x04I/1B7GkNz/TOy+zpJ0h0B7VHdxyO5JYiI/1qsatX/FE2aJdQYMYOtqfDH7ZH5UUKIqo538OKvE2M4MlBR/aVE4z4QDKfE/1kYrOfvVGfDzF/FWHfUrcqB8kdQMvk8vCoM8yYZsX4KE1aoJbNM2pWv2tpr9JE8b/VQgUyHOgPYAha+UOmZki4Sfl9H62687EIWdbM57ZxmwIiBp60SrJLiBfZon9JqXKdtJOKj0CRokQiBnONNXCVerLFeBNQfeKRw8tgJXf+QPohMGYkSDdc8hTgdbmhTwB1Vv01stlYK12QMNRCovlp/fcmpB72Phlq+/3p5pqMzknw+qm5QAz7JnpZJCFHit52gHwAkKRkVPB3HF2rfLrdTYz5c9Bok1ICAY9My47eLFdduIe99V52cjMLQuUmNFBPrDdyZKVqIHJ/wtWO/wIFpAVGSJMHctyEKmeJVc1IQN74Wm00PrpPackHdO3G41bBmkp5pqUdsSgSkwdfNVqv0cMcSe04NrRGNKMcZ7TA/CMaP1YnhxvVE+z8aksJqSJ+gdplvuwl40y5C8UEHeAi1V8Q0Bf4YvYRgOVIWm2Lzjdn2z9PWLGcStUj11/1hthk2li4V3mgm2Cr2IZme2sn4rZmJ6dexGP1nk+ZYOq0xLE8F7oex9gyDN+A/6zHqnuhO/X08qye0gochMr8U89Qvj2c0L3P2mjCea2H1mEriAJPqMPMKIinh1lQJEZufnfCcPxbZLKTtl6zHtHgOztejd1gV/nUyCVKD4MCMfBDy9C/Af8pWx6akOg/QSQNIGA2AI6zprHn9zEjpFIzXJYvruVI22Yt6oF9Xnt7Ki82wRK2M96r4kj6cwSs4exMPGv8fWMrFTm0Br6p6T+HZsxyyn2ChuPIgpfisnce/ZaU/0xCZhK/K79+TK2GeeChq5oEpua/1tx4+kDHi7H9381pLJmy2oXW060c2mmwA9+EpcuwEDhr8fsnghbv41u7b1NhEmWNVUy29Dwaz61PPGUdh5DsvaKLWC+raZ/6UEKPw+tiABJ5o6u2jAWgmEYmmJCKapNgtfPc6D+O0aHH9oqh6u4+8NRAhusPZzDGWBr6AT4pexgWFeEhZhn6bXM9HhpUe0IhOTw5D+tqXrTlNon4kjYibiMUFy1h2YyYS3IEdu1J4xqvo0rFyCxF1C+P6ubc0tClRPkXg==";
string actualPlain = Utilities.AESUtility.DecryptWithGCM(key: key, nonce: nonce, aad: aad, cipherText: cipherText);
string actualPlain = Utilities.AESUtility.DecryptWithGCM(encodingKey: new EncodedString(key, EncodingKinds.Literal), encodingNonce: new EncodedString(nonce, EncodingKinds.Literal), encodingAssociatedData: new EncodedString(associatedData, EncodingKinds.Literal), encodingCipher: new EncodedString(cipherText, EncodingKinds.Base64))!;
string expectedPlain = "-----BEGIN CERTIFICATE-----\nMIID3DCCAsSgAwIBAgIUGZNrTcamx3sFSJsQli3v9C6gZe8wDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\nFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\nQ0EwHhcNMjAwODAxMDczNTE4WhcNMjUwNzMxMDczNTE4WjBuMRgwFgYDVQQDDA9U\nZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl\nbnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo\nZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOAYHqxCqaRzoTIvgV\nixaYJJvmvHbiczbx5MQ9XL1ITSFxkTsNsk7RKHnO7eBS5imheJgQwd22Ky+XclSe\n7B4odssu/l/+gHo2gooTYrrCpQrOkpvGMf8R8aI56BQIF+vsomDvVq1NojHV2Fql\njMwFXzhj2EmU6p6gDv9iL7q1NrfnxFx8iJe4OhIB5Ek4qn1xXxrTUhiULd2vXlbI\nXhRetZSNcJsLt5Rw7D7c8F+aX2JchfeqsZECwKW7bSjMbVWWC6M9MgkB/aId8P0y\n7qEiiXFJkfkg1I/E1ud2apopsid5tdCyRRR6+MhhX2EC8S04MN4soUT7haqNNxX2\nrKHnAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DBlBgNVHR8EXjBc\nMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5jb20uY24vcHVibGljL2l0cnVzY3Js\nP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJ\nKoZIhvcNAQELBQADggEBAJyg2z4oLQmPfftLQWyzbUc9ONhRMtfA+tVlVBgtLLKn\nWuDlsmEntheM07fu84F4pcfs3yHzjD7pAOFbO4Yt1yhQ50DK35sjbRWepPdWJZLl\nni7KBctcmm0o4zq37oB7vonmBEbFqYs9DaINYOjgI3J25iSBkPVC7dBbvFj2xB0L\ncIcXipq30tDdC/oUem27MNzwZAt49WthKhw6u3HSkcE5cO4LyYTsJhSyG/7LXwvV\nMgX4Jyzo0SSiGOU1/beaZssTVI8sTPJVlHWjhNE3Lc2SaAlKGfGwvt0X3cEZEF+7\noEZIFTkkAF2JhqfnpR3gST0G8Umq1SaVtCPP/zVI8x0=\n-----END CERTIFICATE-----";
Assert.Equal(expectedPlain, actualPlain);

View File

@ -2,6 +2,8 @@ using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
{
using SKIT.FlurlHttpClient.Primitives;
public class TestCase_ToolsRSAUtilityTests
{
// 此处测试的 RSA 公钥/私钥是自签名生成的,仅供执行 RSA 相关的单元测试,不能用于调用企业微信 API
@ -12,7 +14,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.UnitTests
public void TestRSADecrypt()
{
string cipherText = "ewwZ8LmXVJpkJpj/JWcz16L4bePAGcf3Fi2EKyC6AS3JsF5u4aku7iOYqtcAczjoYwE1fqSadRd6YTrWr3tLP3uWFYmhqthQoaAcjmQS0vHYRFeS1V7q5hbziVLRp7C42S4YrvqXAmSmUyjPUXG5tXFVchARVkTr1F53HGoPP+iBg+i8y0uJK4FgiuKraFgdtKofv/k5/30xKzRHxdLFCFt1rF7wL+Hk/7Bl0tFZM/rfhmuvwbf46zWhxKKviAge+61tEot4QCSBLnAFpPuSQsTOOSOrlCl92DwW54dWdlWwhqkTVHdm6pXEdUE66y1yoZkXfpqjnONjta0njqN/Jw==";
string actualPlain = Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, cipherText);
string actualPlain = Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, new EncodedString(cipherText, EncodingKinds.Base64))!;
string expectedPlain = "RsaDecryptTest";
Assert.Equal(expectedPlain, actualPlain);