Skip to content

Commit ae5bb82

Browse files
committed
introduce optimized IAuthenticatedEncryptor
1 parent 6142309 commit ae5bb82

File tree

5 files changed

+112
-103
lines changed

5 files changed

+112
-103
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#nullable enable
2-
Microsoft.AspNetCore.DataProtection.IDataProtector.GetProtectedSize(System.ReadOnlySpan<byte> plainText) -> int
3-
Microsoft.AspNetCore.DataProtection.IDataProtector.TryProtect(System.ReadOnlySpan<byte> plainText, System.Span<byte> destination, out int bytesWritten) -> bool
2+
Microsoft.AspNetCore.DataProtection.IOptimizedDataProtector
3+
Microsoft.AspNetCore.DataProtection.IOptimizedDataProtector.GetProtectedSize(System.ReadOnlySpan<byte> plainText) -> int
4+
Microsoft.AspNetCore.DataProtection.IOptimizedDataProtector.TryProtect(System.ReadOnlySpan<byte> plainText, System.Span<byte> destination, out int bytesWritten) -> bool

src/DataProtection/DataProtection/src/AuthenticatedEncryption/IAuthenticatedEncryptor.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,4 @@ public interface IAuthenticatedEncryptor
3232
/// <returns>The ciphertext blob, including authentication tag.</returns>
3333
/// <remarks>All cryptography-related exceptions should be homogenized to CryptographicException.</remarks>
3434
byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData);
35-
36-
#if NET10_0_OR_GREATER
37-
/// <summary>
38-
/// Returns the size of the encrypted data for a given plaintext length.
39-
/// </summary>
40-
/// <param name="plainTextLength">Length of the plain text that will be encrypted later</param>
41-
/// <returns>The length of the encrypted data</returns>
42-
int GetEncryptedSize(int plainTextLength);
43-
44-
/// <summary>
45-
/// Attempts to encrypt and tamper-proof a piece of data.
46-
/// </summary>
47-
/// <param name="plaintext">The input to encrypt.</param>
48-
/// <param name="additionalAuthenticatedData">
49-
/// A piece of data which will not be included in
50-
/// the returned ciphertext but which will still be covered by the authentication tag.
51-
/// This input may be zero bytes in length. The same AAD must be specified in the corresponding decryption call.
52-
/// </param>
53-
/// <param name="destination">The ciphertext blob, including authentication tag.</param>
54-
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
55-
/// <returns>true if destination is long enough to receive the encrypted data; otherwise, false.</returns>
56-
bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);
57-
#endif
5835
}

src/DataProtection/DataProtection/src/AuthenticatedEncryption/IOptimizedAuthenticatedEncryptor.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
88
/// <summary>
99
/// An optimized encryptor that can avoid buffer allocations in common code paths.
1010
/// </summary>
11-
internal interface IOptimizedAuthenticatedEncryptor : IAuthenticatedEncryptor
11+
#if NET10_0_OR_GREATER
12+
public
13+
#else
14+
internal
15+
#endif
16+
interface IOptimizedAuthenticatedEncryptor : IAuthenticatedEncryptor
1217
{
1318
/// <summary>
1419
/// Encrypts and tamper-proofs a piece of data.
@@ -37,5 +42,28 @@ internal interface IOptimizedAuthenticatedEncryptor : IAuthenticatedEncryptor
3742
///
3843
/// All cryptography-related exceptions should be homogenized to CryptographicException.
3944
/// </remarks>
40-
byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize);
45+
internal byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize);
46+
47+
#if NET10_0_OR_GREATER
48+
/// <summary>
49+
/// Returns the size of the encrypted data for a given plaintext length.
50+
/// </summary>
51+
/// <param name="plainTextLength">Length of the plain text that will be encrypted later</param>
52+
/// <returns>The length of the encrypted data</returns>
53+
int GetEncryptedSize(int plainTextLength);
54+
55+
/// <summary>
56+
/// Attempts to encrypt and tamper-proof a piece of data.
57+
/// </summary>
58+
/// <param name="plaintext">The input to encrypt.</param>
59+
/// <param name="additionalAuthenticatedData">
60+
/// A piece of data which will not be included in
61+
/// the returned ciphertext but which will still be covered by the authentication tag.
62+
/// This input may be zero bytes in length. The same AAD must be specified in the corresponding decryption call.
63+
/// </param>
64+
/// <param name="destination">The ciphertext blob, including authentication tag.</param>
65+
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
66+
/// <returns>true if destination is long enough to receive the encrypted data; otherwise, false.</returns>
67+
bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);
68+
#endif
4169
}

src/DataProtection/DataProtection/src/Managed/AesGcmAuthenticatedEncryptor.cs

Lines changed: 76 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -141,46 +141,48 @@ public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> addition
141141
}
142142
}
143143

144-
public int GetEncryptedSize(int plainTextLength)
145-
{
146-
// A buffer to hold the key modifier, nonce, encrypted data, and tag.
147-
// In GCM, the encrypted output will be the same length as the plaintext input.
148-
return checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plainTextLength + TAG_SIZE_IN_BYTES));
149-
}
150-
151-
public bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
144+
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
152145
{
153-
bytesWritten = 0;
146+
plaintext.Validate();
147+
additionalAuthenticatedData.Validate();
154148

155149
try
156150
{
157-
// Generate random key modifier and nonce
151+
// Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag.
152+
// In GCM, the encrypted output will be the same length as the plaintext input.
153+
var retVal = new byte[checked(preBufferSize + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES + postBufferSize)];
154+
int keyModifierOffset; // position in ciphertext.Array where key modifier begins
155+
int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins
156+
int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins
157+
int tagOffset; // position in ciphertext.Array where encrypted data ends
158+
159+
checked
160+
{
161+
keyModifierOffset = plaintext.Offset + (int)preBufferSize;
162+
nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
163+
encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES;
164+
tagOffset = encryptedDataOffset + plaintext.Count;
165+
}
166+
167+
// Randomly generate the key modifier and nonce
158168
var keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES);
159169
var nonceBytes = _genRandom.GenRandom(NONCE_SIZE_IN_BYTES);
160170

161-
// KeyModifier and nonce to destination
162-
keyModifier.CopyTo(destination.Slice(bytesWritten, KEY_MODIFIER_SIZE_IN_BYTES));
163-
bytesWritten += KEY_MODIFIER_SIZE_IN_BYTES;
164-
nonceBytes.CopyTo(destination.Slice(bytesWritten, NONCE_SIZE_IN_BYTES));
165-
bytesWritten += NONCE_SIZE_IN_BYTES;
171+
Buffer.BlockCopy(keyModifier, 0, retVal, (int)preBufferSize, keyModifier.Length);
172+
Buffer.BlockCopy(nonceBytes, 0, retVal, (int)preBufferSize + keyModifier.Length, nonceBytes.Length);
166173

167-
// At this point, destination := { keyModifier | nonce | _____ | _____ }
174+
// At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer }
168175

169176
// Use the KDF to generate a new symmetric block cipher key
170177
// We'll need a temporary buffer to hold the symmetric encryption subkey
171-
Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
172-
? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
173-
: new byte[_keyDerivationKey.Length];
174-
var derivedKey = _derivedkeySizeInBytes <= 256
175-
? stackalloc byte[256].Slice(0, _derivedkeySizeInBytes)
176-
: new byte[_derivedkeySizeInBytes];
177-
178-
fixed (byte* decryptedKdkUnsafe = decryptedKdk)
178+
var decryptedKdk = new byte[_keyDerivationKey.Length];
179+
var derivedKey = new byte[_derivedkeySizeInBytes];
180+
fixed (byte* __unused__1 = decryptedKdk)
179181
fixed (byte* __unused__2 = derivedKey)
180182
{
181183
try
182184
{
183-
_keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
185+
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
184186
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
185187
kdk: decryptedKdk,
186188
label: additionalAuthenticatedData,
@@ -189,25 +191,22 @@ public bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> addition
189191
operationSubkey: derivedKey,
190192
validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
191193

192-
// Perform GCM encryption. Destination buffer expected structure:
193-
// { keyModifier | nonce | encryptedData | authenticationTag }
194-
var nonce = destination.Slice(KEY_MODIFIER_SIZE_IN_BYTES, NONCE_SIZE_IN_BYTES);
195-
var encrypted = destination.Slice(bytesWritten, plaintext.Length);
196-
var tag = destination.Slice(bytesWritten + plaintext.Length, TAG_SIZE_IN_BYTES);
197-
194+
// do gcm
195+
var nonce = new Span<byte>(retVal, nonceOffset, NONCE_SIZE_IN_BYTES);
196+
var tag = new Span<byte>(retVal, tagOffset, TAG_SIZE_IN_BYTES);
197+
var encrypted = new Span<byte>(retVal, encryptedDataOffset, plaintext.Count);
198198
using var aes = new AesGcm(derivedKey, TAG_SIZE_IN_BYTES);
199199
aes.Encrypt(nonce, plaintext, encrypted, tag);
200200

201-
// At this point, destination := { keyModifier | nonce | encryptedData | authenticationTag }
201+
// At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer }
202202
// And we're done!
203-
bytesWritten += plaintext.Length + TAG_SIZE_IN_BYTES;
204-
return true;
203+
return retVal;
205204
}
206205
finally
207206
{
208207
// delete since these contain secret material
209-
decryptedKdk.Clear();
210-
derivedKey.Clear();
208+
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
209+
Array.Clear(derivedKey, 0, derivedKey.Length);
211210
}
212211
}
213212
}
@@ -218,48 +217,50 @@ public bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> addition
218217
}
219218
}
220219

221-
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
220+
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
221+
=> Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
222+
223+
#if NET10_0_OR_GREATER
224+
public int GetEncryptedSize(int plainTextLength)
222225
{
223-
plaintext.Validate();
224-
additionalAuthenticatedData.Validate();
226+
// A buffer to hold the key modifier, nonce, encrypted data, and tag.
227+
// In GCM, the encrypted output will be the same length as the plaintext input.
228+
return checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plainTextLength + TAG_SIZE_IN_BYTES));
229+
}
230+
231+
public bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
232+
{
233+
bytesWritten = 0;
225234

226235
try
227236
{
228-
// Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag.
229-
// In GCM, the encrypted output will be the same length as the plaintext input.
230-
var retVal = new byte[checked(preBufferSize + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES + postBufferSize)];
231-
int keyModifierOffset; // position in ciphertext.Array where key modifier begins
232-
int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins
233-
int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins
234-
int tagOffset; // position in ciphertext.Array where encrypted data ends
235-
236-
checked
237-
{
238-
keyModifierOffset = plaintext.Offset + (int)preBufferSize;
239-
nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
240-
encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES;
241-
tagOffset = encryptedDataOffset + plaintext.Count;
242-
}
243-
244-
// Randomly generate the key modifier and nonce
237+
// Generate random key modifier and nonce
245238
var keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES);
246239
var nonceBytes = _genRandom.GenRandom(NONCE_SIZE_IN_BYTES);
247240

248-
Buffer.BlockCopy(keyModifier, 0, retVal, (int)preBufferSize, keyModifier.Length);
249-
Buffer.BlockCopy(nonceBytes, 0, retVal, (int)preBufferSize + keyModifier.Length, nonceBytes.Length);
241+
// KeyModifier and nonce to destination
242+
keyModifier.CopyTo(destination.Slice(bytesWritten, KEY_MODIFIER_SIZE_IN_BYTES));
243+
bytesWritten += KEY_MODIFIER_SIZE_IN_BYTES;
244+
nonceBytes.CopyTo(destination.Slice(bytesWritten, NONCE_SIZE_IN_BYTES));
245+
bytesWritten += NONCE_SIZE_IN_BYTES;
250246

251-
// At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer }
247+
// At this point, destination := { keyModifier | nonce | _____ | _____ }
252248

253249
// Use the KDF to generate a new symmetric block cipher key
254250
// We'll need a temporary buffer to hold the symmetric encryption subkey
255-
var decryptedKdk = new byte[_keyDerivationKey.Length];
256-
var derivedKey = new byte[_derivedkeySizeInBytes];
257-
fixed (byte* __unused__1 = decryptedKdk)
251+
Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
252+
? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
253+
: new byte[_keyDerivationKey.Length];
254+
var derivedKey = _derivedkeySizeInBytes <= 256
255+
? stackalloc byte[256].Slice(0, _derivedkeySizeInBytes)
256+
: new byte[_derivedkeySizeInBytes];
257+
258+
fixed (byte* decryptedKdkUnsafe = decryptedKdk)
258259
fixed (byte* __unused__2 = derivedKey)
259260
{
260261
try
261262
{
262-
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
263+
_keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
263264
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
264265
kdk: decryptedKdk,
265266
label: additionalAuthenticatedData,
@@ -268,22 +269,25 @@ public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additiona
268269
operationSubkey: derivedKey,
269270
validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
270271

271-
// do gcm
272-
var nonce = new Span<byte>(retVal, nonceOffset, NONCE_SIZE_IN_BYTES);
273-
var tag = new Span<byte>(retVal, tagOffset, TAG_SIZE_IN_BYTES);
274-
var encrypted = new Span<byte>(retVal, encryptedDataOffset, plaintext.Count);
272+
// Perform GCM encryption. Destination buffer expected structure:
273+
// { keyModifier | nonce | encryptedData | authenticationTag }
274+
var nonce = destination.Slice(KEY_MODIFIER_SIZE_IN_BYTES, NONCE_SIZE_IN_BYTES);
275+
var encrypted = destination.Slice(bytesWritten, plaintext.Length);
276+
var tag = destination.Slice(bytesWritten + plaintext.Length, TAG_SIZE_IN_BYTES);
277+
275278
using var aes = new AesGcm(derivedKey, TAG_SIZE_IN_BYTES);
276279
aes.Encrypt(nonce, plaintext, encrypted, tag);
277280

278-
// At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer }
281+
// At this point, destination := { keyModifier | nonce | encryptedData | authenticationTag }
279282
// And we're done!
280-
return retVal;
283+
bytesWritten += plaintext.Length + TAG_SIZE_IN_BYTES;
284+
return true;
281285
}
282286
finally
283287
{
284288
// delete since these contain secret material
285-
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
286-
Array.Clear(derivedKey, 0, derivedKey.Length);
289+
decryptedKdk.Clear();
290+
derivedKey.Clear();
287291
}
288292
}
289293
}
@@ -293,9 +297,7 @@ public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additiona
293297
throw Error.CryptCommon_GenericError(ex);
294298
}
295299
}
296-
297-
public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
298-
=> Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
300+
#endif
299301

300302
public void Dispose()
301303
{
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#nullable enable
2-
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.GetEncryptedSize(int plainTextLength) -> int
3-
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.TryEncrypt(System.ReadOnlySpan<byte> plaintext, System.ReadOnlySpan<byte> additionalAuthenticatedData, System.Span<byte> destination, out int bytesWritten) -> bool
2+
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IOptimizedAuthenticatedEncryptor
3+
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IOptimizedAuthenticatedEncryptor.GetEncryptedSize(int plainTextLength) -> int
4+
Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IOptimizedAuthenticatedEncryptor.TryEncrypt(System.ReadOnlySpan<byte> plaintext, System.ReadOnlySpan<byte> additionalAuthenticatedData, System.Span<byte> destination, out int bytesWritten) -> bool

0 commit comments

Comments
 (0)