Skip to content

Commit f83c97f

Browse files
authored
perf: improve ManagedAuthenticatedEncryptor Decrypt() and Encrypt() flow (#59424)
1 parent 04ed093 commit f83c97f

13 files changed

+407
-144
lines changed

eng/Dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ and are generated based on the last package release.
8282
<LatestPackageReference Include="System.DirectoryServices.Protocols" />
8383
<LatestPackageReference Include="System.IdentityModel.Tokens.Jwt" />
8484
<LatestPackageReference Include="System.IO.Pipelines" />
85+
<LatestPackageReference Include="System.Memory" />
8586
<LatestPackageReference Include="System.Net.Http" />
8687
<LatestPackageReference Include="System.Net.Http.Json" />
8788
<LatestPackageReference Include="System.Net.Sockets" />

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
<SystemCommandlineExperimentalVersion>0.3.0-alpha.19317.1</SystemCommandlineExperimentalVersion>
219219
<SystemComponentModelVersion>4.3.0</SystemComponentModelVersion>
220220
<SystemNetHttpVersion>4.3.4</SystemNetHttpVersion>
221+
<SystemMemoryVersion>4.6.0</SystemMemoryVersion>
221222
<SystemNetSocketsVersion>4.3.0</SystemNetSocketsVersion>
222223
<SystemSecurityCryptographyX509CertificatesVersion>4.3.2</SystemSecurityCryptographyX509CertificatesVersion>
223224
<SystemRuntimeInteropServicesRuntimeInformationVersion>4.3.0</SystemRuntimeInteropServicesRuntimeInformationVersion>

src/DataProtection/Cryptography.Internal/src/CryptoUtil.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,18 @@ public static bool TimeConstantBuffersAreEqual(byte* bufA, byte* bufB, uint coun
9292
}
9393

9494
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
95-
public static bool TimeConstantBuffersAreEqual(byte[] bufA, int offsetA, int countA, byte[] bufB, int offsetB, int countB)
95+
public static bool TimeConstantBuffersAreEqual(ReadOnlySpan<byte> bufA, ReadOnlySpan<byte> bufB)
9696
{
97-
// Technically this is an early exit scenario, but it means that the caller did something bizarre.
98-
// An error at the call site isn't usable for timing attacks.
99-
Assert(countA == countB, "countA == countB");
97+
// This early exit handles unexpected input without introducing timing vulnerabilities.
98+
Assert(bufA.Length == bufB.Length, "countA == countB");
10099

101100
#if NETCOREAPP
102-
unsafe
103-
{
104-
return CryptographicOperations.FixedTimeEquals(
105-
bufA.AsSpan(start: offsetA, length: countA),
106-
bufB.AsSpan(start: offsetB, length: countB));
107-
}
101+
return CryptographicOperations.FixedTimeEquals(bufA, bufB);
108102
#else
109103
bool areEqual = true;
110-
for (int i = 0; i < countA; i++)
104+
for (int i = 0; i < bufA.Length; i++)
111105
{
112-
areEqual &= (bufA[offsetA + i] == bufB[offsetB + i]);
106+
areEqual &= (bufA[i] == bufB[i]);
113107
}
114108
return areEqual;
115109
#endif

src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@
1919
<InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Extensions.Tests" />
2020
<InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Tests" />
2121
</ItemGroup>
22+
23+
<ItemGroup Condition="'$(TargetFramework)' != '$(DefaultNetCoreTargetFramework)'">
24+
<Reference Include="System.Memory" />
25+
</ItemGroup>
2226
</Project>

src/DataProtection/Cryptography.Internal/test/CryptoUtilTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using Xunit;
56

67
namespace Microsoft.AspNetCore.Cryptography;
@@ -15,7 +16,7 @@ public void TimeConstantBuffersAreEqual_Array_Equal()
1516
byte[] b = new byte[] { 0xAB, 0xCD, 0x23, 0x45, 0x67, 0xEF };
1617

1718
// Act & assert
18-
Assert.True(CryptoUtil.TimeConstantBuffersAreEqual(a, 1, 3, b, 2, 3));
19+
Assert.True(CryptoUtil.TimeConstantBuffersAreEqual(a.AsSpan(1, 3), b.AsSpan(2, 3)));
1920
}
2021

2122
[Fact]
@@ -25,7 +26,7 @@ public void TimeConstantBuffersAreEqual_Array_Unequal()
2526
byte[] b = new byte[] { 0xAB, 0xCD, 0x23, 0xFF, 0x67, 0xEF };
2627

2728
// Act & assert
28-
Assert.False(CryptoUtil.TimeConstantBuffersAreEqual(a, 1, 3, b, 2, 3));
29+
Assert.False(CryptoUtil.TimeConstantBuffersAreEqual(a.AsSpan(1, 3), b.AsSpan(2, 3)));
2930
}
3031

3132
[Fact]

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthentica
3535
// 256 00-01-00-00-00-20-00-00-00-0C-00-00-00-10-00-00-00-10-E7-DC-CE-66-DF-85-5A-32-3A-6B-B7-BD-7A-59-BE-45
3636
private static readonly byte[] AES_256_GCM_Header = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0xE7, 0xDC, 0xCE, 0x66, 0xDF, 0x85, 0x5A, 0x32, 0x3A, 0x6B, 0xB7, 0xBD, 0x7A, 0x59, 0xBE, 0x45 };
3737

38-
private static readonly Func<byte[], HashAlgorithm> _kdkPrfFactory = key => new HMACSHA512(key); // currently hardcoded to SHA512
39-
4038
private readonly byte[] _contextHeader;
4139

4240
private readonly Secret _keyDerivationKey;
@@ -112,13 +110,13 @@ public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> addition
112110
try
113111
{
114112
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
115-
ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader(
113+
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
116114
kdk: decryptedKdk,
117115
label: additionalAuthenticatedData,
118116
contextHeader: _contextHeader,
119-
context: keyModifier,
120-
prfFactory: _kdkPrfFactory,
121-
output: new ArraySegment<byte>(derivedKey));
117+
contextData: keyModifier,
118+
operationSubkey: derivedKey,
119+
validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
122120

123121
// Perform the decryption operation
124122
var nonce = new Span<byte>(ciphertext.Array, nonceOffset, NONCE_SIZE_IN_BYTES);
@@ -185,13 +183,13 @@ public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additiona
185183
try
186184
{
187185
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
188-
ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader(
186+
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
189187
kdk: decryptedKdk,
190188
label: additionalAuthenticatedData,
191189
contextHeader: _contextHeader,
192-
context: keyModifier,
193-
prfFactory: _kdkPrfFactory,
194-
output: new ArraySegment<byte>(derivedKey));
190+
contextData: keyModifier,
191+
operationSubkey: derivedKey,
192+
validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
195193

196194
// do gcm
197195
var nonce = new Span<byte>(retVal, nonceOffset, NONCE_SIZE_IN_BYTES);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
5+
46
namespace Microsoft.AspNetCore.DataProtection.Managed;
57

68
internal interface IManagedGenRandom
79
{
810
byte[] GenRandom(int numBytes);
11+
12+
#if NET10_0_OR_GREATER
13+
void GenRandom(Span<byte> target);
14+
#endif
915
}

0 commit comments

Comments
 (0)