-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAesEncryptionProvider.cs
More file actions
230 lines (210 loc) · 7.2 KB
/
Copy pathAesEncryptionProvider.cs
File metadata and controls
230 lines (210 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Copyright (c) 2023-2026 ktsu-dev contributors
namespace ktsu.Essentials.EncryptionProviders.Aes;
using ktsu.Essentials;
using System;
using System.IO;
using System.Security.Cryptography;
/// <summary>
/// An encryption provider that uses AES for data encryption and decryption.
/// </summary>
/// <remarks>
/// This type is stateless and safe to share across threads — every operation creates its own
/// <see cref="System.Security.Cryptography.Aes"/> instance from the caller-supplied key and IV.
/// It is therefore safe to register as a singleton.
/// </remarks>
public class AesEncryptionProvider : IEncryptionProvider
{
private const int KeySize = 32; // 256 bits
private const int IVSize = 16; // 128 bits
private const int BlockSizeBytes = 16; // AES block size is always 128 bits
/// <inheritdoc/>
/// <remarks>CBC with PKCS7 always pads to the next whole block, adding a full block when already aligned.</remarks>
public int GetMaxEncryptedLength(int sourceLength) => ((sourceLength / BlockSizeBytes) + 1) * BlockSizeBytes;
/// <summary>
/// Generates a new encryption key.
/// </summary>
/// <returns>A new encryption key.</returns>
public byte[] GenerateKey()
{
byte[] key = new byte[KeySize];
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
rng.GetBytes(key);
return key;
}
/// <summary>
/// Generates a new initialization vector.
/// </summary>
/// <returns>A new initialization vector.</returns>
public byte[] GenerateIV()
{
byte[] iv = new byte[IVSize];
using RandomNumberGenerator rng = RandomNumberGenerator.Create();
rng.GetBytes(iv);
return iv;
}
/// <summary>
/// Tries to encrypt the data from the span and write the result to the destination.
/// </summary>
/// <param name="data">The data to encrypt.</param>
/// <param name="key">The key to use for encryption.</param>
/// <param name="iv">The initialization vector to use for encryption.</param>
/// <param name="destination">The destination to write the encrypted data to.</param>
/// <param name="bytesWritten">The number of bytes written to <paramref name="destination"/>.</param>
/// <returns>True if the encryption was successful, false otherwise.</returns>
public bool TryEncrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
if (key.Length != KeySize || iv.Length != IVSize)
{
return false;
}
try
{
using System.Security.Cryptography.Aes aes = System.Security.Cryptography.Aes.Create();
using ICryptoTransform encryptor = aes.CreateEncryptor(key.ToArray(), iv.ToArray());
byte[] encryptedData = encryptor.TransformFinalBlock(data.ToArray(), 0, data.Length);
if (encryptedData.Length > destination.Length)
{
return false;
}
encryptedData.CopyTo(destination);
bytesWritten = encryptedData.Length;
return true;
}
catch (ArgumentException)
{
return false;
}
catch (CryptographicException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Tries to encrypt the data from the stream and write the result to the destination.
/// </summary>
/// <param name="data">The data to encrypt.</param>
/// <param name="key">The key to use for encryption.</param>
/// <param name="iv">The initialization vector to use for encryption.</param>
/// <param name="destination">The destination to write the encrypted data to.</param>
/// <returns>True if the encryption was successful, false otherwise.</returns>
public bool TryEncrypt(Stream data, ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, Stream destination)
{
if (data is null || destination is null || key.Length != KeySize || iv.Length != IVSize)
{
return false;
}
try
{
using System.Security.Cryptography.Aes aes = System.Security.Cryptography.Aes.Create();
using ICryptoTransform encryptor = aes.CreateEncryptor(key.ToArray(), iv.ToArray());
using CryptoStream cryptoStream = new(destination, encryptor, CryptoStreamMode.Write, leaveOpen: true);
data.CopyTo(cryptoStream);
return true;
}
catch (ArgumentException)
{
return false;
}
catch (CryptographicException)
{
return false;
}
catch (IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Tries to decrypt the data from the span and write the result to the destination.
/// </summary>
/// <param name="data">The data to decrypt.</param>
/// <param name="key">The key to use for decryption.</param>
/// <param name="iv">The initialization vector to use for decryption.</param>
/// <param name="destination">The destination to write the decrypted data to.</param>
/// <param name="bytesWritten">The number of bytes written to <paramref name="destination"/>.</param>
/// <returns>True if the decryption was successful, false otherwise.</returns>
public bool TryDecrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
if (key.Length != KeySize || iv.Length != IVSize || data.IsEmpty)
{
return false;
}
try
{
// The ciphertext is exactly the span the caller passed. Earlier versions had to guess its
// length by trimming trailing zeros, because the API could not report how many bytes the
// matching encrypt call wrote; that corrupted any ciphertext ending in a zero byte.
using System.Security.Cryptography.Aes aes = System.Security.Cryptography.Aes.Create();
using ICryptoTransform decryptor = aes.CreateDecryptor(key.ToArray(), iv.ToArray());
byte[] decryptedData = decryptor.TransformFinalBlock(data.ToArray(), 0, data.Length);
if (decryptedData.Length > destination.Length)
{
return false;
}
decryptedData.CopyTo(destination);
bytesWritten = decryptedData.Length;
return true;
}
catch (ArgumentException)
{
return false;
}
catch (CryptographicException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Tries to decrypt the data from the stream and write the result to the destination.
/// </summary>
/// <param name="data">The data to decrypt.</param>
/// <param name="key">The key to use for decryption.</param>
/// <param name="iv">The initialization vector to use for decryption.</param>
/// <param name="destination">The destination to write the decrypted data to.</param>
/// <returns>True if the decryption was successful, false otherwise.</returns>
public bool TryDecrypt(Stream data, ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, Stream destination)
{
if (data is null || destination is null || key.Length != KeySize || iv.Length != IVSize)
{
return false;
}
try
{
using System.Security.Cryptography.Aes aes = System.Security.Cryptography.Aes.Create();
using ICryptoTransform decryptor = aes.CreateDecryptor(key.ToArray(), iv.ToArray());
using CryptoStream cryptoStream = new(data, decryptor, CryptoStreamMode.Read, leaveOpen: true);
cryptoStream.CopyTo(destination);
return true;
}
catch (ArgumentException)
{
return false;
}
catch (CryptographicException)
{
return false;
}
catch (IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
}