Skip to content

Commit be37ab0

Browse files
committed
feat(wxapi): 新增安全鉴权模式所需的 AES、RSA、SM2、SM4 等算法工具类
1 parent 2bd10cc commit be37ab0

File tree

9 files changed

+1099
-2
lines changed

9 files changed

+1099
-2
lines changed

src/SKIT.FlurlHttpClient.Wechat.Api/SKIT.FlurlHttpClient.Wechat.Api.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
</ItemGroup>
4141

4242
<ItemGroup>
43+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.1" />
4344
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0" />
4445
</ItemGroup>
4546

src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/AESUtility.cs

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
22
using System.Security.Cryptography;
3+
using System.Text;
4+
using Org.BouncyCastle.Crypto;
5+
using Org.BouncyCastle.Crypto.Parameters;
6+
using Org.BouncyCastle.Security;
37

48
namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
59
{
@@ -10,11 +14,16 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
1014
/// </summary>
1115
public static class AESUtility
1216
{
17+
/// <summary>
18+
/// 填充模式:NoPadding。
19+
/// </summary>
20+
public const string PADDING_MODE_NOPADDING = "NoPadding";
21+
1322
/// <summary>
1423
/// 基于 CBC 模式解密数据。
1524
/// </summary>
1625
/// <param name="keyBytes">AES 密钥字节数组。</param>
17-
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
26+
/// <param name="ivBytes">初始化向量字节数组。</param>
1827
/// <param name="cipherBytes">待解密数据字节数组。</param>
1928
/// <returns>解密后的数据字节数组。</returns>
2029
public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes)
@@ -56,11 +65,86 @@ public static EncodedString DecryptWithCBC(EncodedString encodingKey, EncodedStr
5665
return EncodedString.ToLiteralString(plainBytes);
5766
}
5867

68+
/// <summary>
69+
/// 基于 GCM 模式解密数据。
70+
/// </summary>
71+
/// <param name="keyBytes">AES 密钥字节数组。</param>
72+
/// <param name="nonceBytes">初始化向量字节数组。</param>
73+
/// <param name="associatedDataBytes">附加数据字节数组。</param>
74+
/// <param name="cipherBytes">待解密数据字节数组。</param>
75+
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
76+
/// <returns>解密后的数据字节数组。</returns>
77+
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_NOPADDING)
78+
{
79+
const int KEY_LENGTH_BYTE = 32;
80+
const int NONCE_LENGTH_BYTE = 12;
81+
const int TAG_LENGTH_BYTE = 16;
82+
83+
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
84+
if (keyBytes.Length != KEY_LENGTH_BYTE) throw new ArgumentException($"Invalid key byte length (expected: {KEY_LENGTH_BYTE}, actual: {keyBytes.Length}).", nameof(keyBytes));
85+
if (nonceBytes is null) throw new ArgumentNullException(nameof(nonceBytes));
86+
if (nonceBytes.Length != NONCE_LENGTH_BYTE) throw new ArgumentException($"Invalid nonce byte length (expected: {NONCE_LENGTH_BYTE}, actual: {nonceBytes.Length}).", nameof(nonceBytes));
87+
if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
88+
if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes));
89+
90+
#if NET5_0_OR_GREATER
91+
using (AesGcm aes = new AesGcm(keyBytes))
92+
{
93+
byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE];
94+
byte[] tagBytes = new byte[TAG_LENGTH_BYTE];
95+
Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length);
96+
Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length);
97+
98+
byte[] plainBytes = new byte[cipherWithoutTagBytes.Length];
99+
aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes);
100+
return plainBytes;
101+
}
102+
#else
103+
IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}");
104+
ICipherParameters cipherParams = new AeadParameters(
105+
new KeyParameter(keyBytes),
106+
TAG_LENGTH_BYTE * 8,
107+
nonceBytes,
108+
associatedDataBytes
109+
);
110+
cipher.Init(false, cipherParams);
111+
byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
112+
int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0);
113+
cipher.DoFinal(plainBytes, len);
114+
return plainBytes;
115+
#endif
116+
}
117+
118+
/// <summary>
119+
/// 基于 GCM 模式解密数据。
120+
/// </summary>
121+
/// <param name="encodingKey">经过编码后的(通常为 Base64)AES 密钥。</param>
122+
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
123+
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
124+
/// <param name="encodingCipher">经过编码后的(通常为 Base64)待解密数据。</param>
125+
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
126+
/// <returns>解密后的数据。</returns>
127+
public static EncodedString DecryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, EncodedString encodingCipher, string paddingMode = PADDING_MODE_NOPADDING)
128+
{
129+
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
130+
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
131+
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
132+
133+
byte[] plainBytes = DecryptWithGCM(
134+
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
135+
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
136+
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
137+
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
138+
paddingMode: paddingMode
139+
);
140+
return EncodedString.ToLiteralString(plainBytes);
141+
}
142+
59143
/// <summary>
60144
/// 基于 CBC 模式加密数据。
61145
/// </summary>
62146
/// <param name="keyBytes">AES 密钥字节数组。</param>
63-
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
147+
/// <param name="ivBytes">初始化向量字节数组。</param>
64148
/// <param name="plainBytes">待加密数据字节数组。</param>
65149
/// <returns>加密后的数据字节数组。</returns>
66150
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes)
@@ -101,5 +185,76 @@ public static EncodedString EncryptWithCBC(EncodedString encodingKey, EncodedStr
101185
);
102186
return EncodedString.ToBase64String(plainBytes);
103187
}
188+
189+
/// <summary>
190+
/// 基于 GCM 模式加密数据。
191+
/// </summary>
192+
/// <param name="keyBytes">AES 密钥字节数组。</param>
193+
/// <param name="nonceBytes">初始化向量字节数组。</param>
194+
/// <param name="associatedDataBytes">附加数据字节数组。</param>
195+
/// <param name="plainBytes">待加密数据字节数组。</param>
196+
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
197+
/// <returns>加密后的数据字节数组。</returns>
198+
public static byte[] EncryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] plainBytes, string paddingMode = PADDING_MODE_NOPADDING)
199+
{
200+
const int TAG_LENGTH_BYTE = 16;
201+
202+
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
203+
if (nonceBytes is null) throw new ArgumentNullException(nameof(nonceBytes));
204+
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
205+
206+
#if NET5_0_OR_GREATER
207+
using (AesGcm aes = new AesGcm(keyBytes))
208+
{
209+
byte[] cipherBytes = new byte[plainBytes.Length];
210+
byte[] tagBytes = new byte[TAG_LENGTH_BYTE];
211+
aes.Encrypt(nonceBytes, plainBytes, cipherBytes, tagBytes, associatedDataBytes);
212+
213+
byte[] cipherWithTagBytes = new byte[cipherBytes.Length + tagBytes.Length];
214+
Buffer.BlockCopy(cipherBytes, 0, cipherWithTagBytes, 0, cipherBytes.Length);
215+
Buffer.BlockCopy(tagBytes, 0, cipherWithTagBytes, cipherBytes.Length, tagBytes.Length);
216+
return cipherWithTagBytes;
217+
}
218+
#else
219+
220+
IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}");
221+
ICipherParameters cipherParams = new AeadParameters(
222+
new KeyParameter(keyBytes),
223+
TAG_LENGTH_BYTE * 8,
224+
nonceBytes,
225+
associatedDataBytes
226+
);
227+
cipher.Init(true, cipherParams);
228+
byte[] cipherBytes = new byte[cipher.GetOutputSize(plainBytes.Length)];
229+
int len = cipher.ProcessBytes(plainBytes, 0, plainBytes.Length, cipherBytes, 0);
230+
cipher.DoFinal(cipherBytes, len);
231+
return cipherBytes;
232+
#endif
233+
}
234+
235+
/// <summary>
236+
/// 基于 GCM 模式加密数据。
237+
/// </summary>
238+
/// <param name="encodingKey">经过编码后的(通常为 Base64)AES 密钥。</param>
239+
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
240+
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
241+
/// <param name="plainData">待加密数据。</param>
242+
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
243+
/// <returns>经过 Base64 编码的加密后的数据。</returns>
244+
public static EncodedString EncryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, string plainData, string paddingMode = PADDING_MODE_NOPADDING)
245+
{
246+
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
247+
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
248+
if (plainData is null) throw new ArgumentNullException(nameof(plainData));
249+
250+
byte[] plainBytes = EncryptWithGCM(
251+
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
252+
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
253+
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
254+
plainBytes: Encoding.UTF8.GetBytes(plainData),
255+
paddingMode: paddingMode
256+
);
257+
return EncodedString.ToBase64String(plainBytes);
258+
}
104259
}
105260
}

0 commit comments

Comments
 (0)