1
1
using System ;
2
2
using System . Security . Cryptography ;
3
+ using System . Text ;
4
+ using Org . BouncyCastle . Crypto ;
5
+ using Org . BouncyCastle . Crypto . Parameters ;
6
+ using Org . BouncyCastle . Security ;
3
7
4
8
namespace SKIT . FlurlHttpClient . Wechat . Api . Utilities
5
9
{
@@ -10,11 +14,16 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
10
14
/// </summary>
11
15
public static class AESUtility
12
16
{
17
+ /// <summary>
18
+ /// 填充模式:NoPadding。
19
+ /// </summary>
20
+ public const string PADDING_MODE_NOPADDING = "NoPadding" ;
21
+
13
22
/// <summary>
14
23
/// 基于 CBC 模式解密数据。
15
24
/// </summary>
16
25
/// <param name="keyBytes">AES 密钥字节数组。</param>
17
- /// <param name="ivBytes">加密使用的初始化向量字节数组 。</param>
26
+ /// <param name="ivBytes">初始化向量字节数组 。</param>
18
27
/// <param name="cipherBytes">待解密数据字节数组。</param>
19
28
/// <returns>解密后的数据字节数组。</returns>
20
29
public static byte [ ] DecryptWithCBC ( byte [ ] keyBytes , byte [ ] ivBytes , byte [ ] cipherBytes )
@@ -56,11 +65,86 @@ public static EncodedString DecryptWithCBC(EncodedString encodingKey, EncodedStr
56
65
return EncodedString . ToLiteralString ( plainBytes ) ;
57
66
}
58
67
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
+
59
143
/// <summary>
60
144
/// 基于 CBC 模式加密数据。
61
145
/// </summary>
62
146
/// <param name="keyBytes">AES 密钥字节数组。</param>
63
- /// <param name="ivBytes">加密使用的初始化向量字节数组 。</param>
147
+ /// <param name="ivBytes">初始化向量字节数组 。</param>
64
148
/// <param name="plainBytes">待加密数据字节数组。</param>
65
149
/// <returns>加密后的数据字节数组。</returns>
66
150
public static byte [ ] EncryptWithCBC ( byte [ ] keyBytes , byte [ ] ivBytes , byte [ ] plainBytes )
@@ -101,5 +185,76 @@ public static EncodedString EncryptWithCBC(EncodedString encodingKey, EncodedStr
101
185
) ;
102
186
return EncodedString . ToBase64String ( plainBytes ) ;
103
187
}
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
+ }
104
259
}
105
260
}
0 commit comments