Prechádzať zdrojové kódy

feat: "Spanify" DataProtector with IBufferWriter<byte> (#64262)

Korolev Dmitry 3 mesiacov pred
rodič
commit
249b29c265
50 zmenil súbory, kde vykonal 3690 pridanie a 1030 odobranie
  1. 5 1
      AspNetCore.slnx
  2. 52 0
      src/DataProtection/Abstractions/src/ISpanDataProtector.cs
  3. 9 0
      src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
  4. 0 0
      src/DataProtection/Abstractions/src/PublicAPI/net10.0/PublicAPI.Shipped.txt
  5. 4 0
      src/DataProtection/Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt
  6. 16 0
      src/DataProtection/Abstractions/src/PublicAPI/net462/PublicAPI.Shipped.txt
  7. 0 0
      src/DataProtection/Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
  8. 16 0
      src/DataProtection/Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
  9. 0 0
      src/DataProtection/Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
  10. 1 0
      src/DataProtection/DataProtection.slnf
  11. 60 0
      src/DataProtection/DataProtection/src/AuthenticatedEncryption/ISpanAuthenticatedEncryptor.cs
  12. 400 233
      src/DataProtection/DataProtection/src/Cng/CbcAuthenticatedEncryptor.cs
  13. 281 147
      src/DataProtection/DataProtection/src/Cng/CngGcmAuthenticatedEncryptor.cs
  14. 0 86
      src/DataProtection/DataProtection/src/Cng/Internal/CngAuthenticatedEncryptorBase.cs
  15. 17 0
      src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtectionProvider.cs
  16. 43 37
      src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtector.cs
  17. 170 0
      src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedSpanDataProtector.cs
  18. 142 76
      src/DataProtection/DataProtection/src/Managed/AesGcmAuthenticatedEncryptor.cs
  19. 453 303
      src/DataProtection/DataProtection/src/Managed/ManagedAuthenticatedEncryptor.cs
  20. 9 3
      src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj
  21. 0 0
      src/DataProtection/DataProtection/src/PublicAPI/net10.0/PublicAPI.Shipped.txt
  22. 4 0
      src/DataProtection/DataProtection/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt
  23. 289 0
      src/DataProtection/DataProtection/src/PublicAPI/net462/PublicAPI.Shipped.txt
  24. 1 0
      src/DataProtection/DataProtection/src/PublicAPI/net462/PublicAPI.Unshipped.txt
  25. 289 0
      src/DataProtection/DataProtection/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
  26. 1 0
      src/DataProtection/DataProtection/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
  27. 3 2
      src/DataProtection/DataProtection/src/SP800_108/SP800_108_CTR_HMACSHA512Extensions.cs
  28. 27 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Aes/AesAuthenticatedEncryptorTests.cs
  29. 2 2
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs
  30. 51 6
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/CbcAuthenticatedEncryptorTests.cs
  31. 0 105
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/CngAuthenticatedEncryptorBaseTests.cs
  32. 20 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/GcmAuthenticatedEncryptorTests.cs
  33. 91 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Internal/RoundtripEncryptionHelpers.cs
  34. 157 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingBasedDataProtectorTests.cs
  35. 66 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Managed/ManagedAuthenticatedEncryptorTests.cs
  36. 3 2
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Microsoft.AspNetCore.DataProtection.Tests.csproj
  37. 98 0
      src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/TestsDataProtectionProvider.cs
  38. 6 5
      src/DataProtection/Extensions/src/TimeLimitedDataProtector.cs
  39. 4 0
      src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/AssemblyInfo.cs
  40. 134 0
      src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/Benchmarks/SpanDataProtectorComparison.cs
  41. 27 0
      src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks.csproj
  42. 19 3
      src/DataProtection/samples/KeyManagementSimulator/Program.cs
  43. 1 3
      src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs
  44. 4 9
      src/Shared/BenchmarkRunner/Program.cs
  45. 169 0
      src/Shared/Buffers/RefPooledArrayBufferWriter.cs
  46. 37 6
      src/Shared/PooledArrayBufferWriter.cs
  47. 1 1
      src/Shared/Shared.slnf
  48. 196 0
      src/Shared/test/Shared.Tests/Buffers/PooledArrayBufferWriterTests.cs
  49. 310 0
      src/Shared/test/Shared.Tests/Buffers/RefPooledArrayBufferWriterTests.cs
  50. 2 0
      src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj

+ 5 - 1
AspNetCore.slnx

@@ -160,6 +160,9 @@
     <Project Path="src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj" />
     <Project Path="src/DataProtection/Abstractions/test/Microsoft.AspNetCore.DataProtection.Abstractions.Tests.csproj" />
   </Folder>
+  <Folder Name="/src/DataProtection/benchmarks/">
+    <Project Path="src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks.csproj" />
+  </Folder>
   <Folder Name="/src/DataProtection/Cryptography.Internal/">
     <Project Path="src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj" />
     <Project Path="src/DataProtection/Cryptography.Internal/test/Microsoft.AspNetCore.Cryptography.Internal.Tests.csproj" />
@@ -1134,6 +1137,7 @@
     <Project Path="src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" />
     <Project Path="src/SignalR/server/StackExchangeRedis/test/Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests.csproj" />
   </Folder>
+  <Folder Name="/src/SiteExtensions/" />
   <Folder Name="/src/SiteExtensions/Microsoft.Web.Xdt.Extensions/">
     <Project Path="src/SiteExtensions/Microsoft.Web.Xdt.Extensions/src/Microsoft.Web.Xdt.Extensions.csproj" />
     <Project Path="src/SiteExtensions/Microsoft.Web.Xdt.Extensions/tests/Microsoft.Web.Xdt.Extensions.Tests.csproj" />
@@ -1181,8 +1185,8 @@
     <Project Path="src/Validation/src/Microsoft.Extensions.Validation.csproj" />
   </Folder>
   <Folder Name="/src/Validation/test/">
-    <Project Path="src/Validation/test/Microsoft.Extensions.Validation.Tests/Microsoft.Extensions.Validation.Tests.csproj" />
     <Project Path="src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj" />
+    <Project Path="src/Validation/test/Microsoft.Extensions.Validation.Tests/Microsoft.Extensions.Validation.Tests.csproj" />
   </Folder>
   <Folder Name="/src/WebEncoders/">
     <Project Path="src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj" />

+ 52 - 0
src/DataProtection/Abstractions/src/ISpanDataProtector.cs

@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NET
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.DataProtection;
+
+/// <summary>
+/// An interface that can provide data protection services.
+/// Is an optimized version of <see cref="IDataProtector"/>.
+/// </summary>
+public interface ISpanDataProtector : IDataProtector
+{
+    /// <summary>
+    /// Cryptographically protects a piece of plaintext data and writes the result to a buffer writer.
+    /// </summary>
+    /// <typeparam name="TWriter">The type of buffer writer to write the protected data to.</typeparam>
+    /// <param name="plaintext">The plaintext data to protect.</param>
+    /// <param name="destination">The buffer writer to which the protected data will be written.</param>
+    /// <remarks>
+    /// This method provides an optimized, streaming alternative to <see cref="IDataProtector.Protect(byte[])"/>.
+    /// Rather than allocating an intermediate buffer, the protected data is written directly to the provided
+    /// buffer writer, which can improve performance and reduce memory allocation pressure.
+    /// The buffer writer is advanced by the total number of bytes written to it.
+    /// </remarks>
+    void Protect<TWriter>(ReadOnlySpan<byte> plaintext, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct;
+
+    /// <summary>
+    /// Cryptographically unprotects a piece of protected data and writes the result to a buffer writer.
+    /// </summary>
+    /// <typeparam name="TWriter">The type of buffer writer to write the unprotected data to.</typeparam>
+    /// <param name="protectedData">The protected data to unprotect.</param>
+    /// <param name="destination">The buffer writer to which the unprotected plaintext will be written.</param>
+    /// <remarks>
+    /// This method provides an optimized, streaming alternative to <see cref="IDataProtector.Unprotect(byte[])"/>.
+    /// Rather than allocating an intermediate buffer, the unprotected plaintext is written directly to the provided
+    /// buffer writer, which can improve performance and reduce memory allocation pressure.
+    /// The buffer writer is advanced by the total number of bytes written to it.
+    /// </remarks>
+    void Unprotect<TWriter>(ReadOnlySpan<byte> protectedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct;
+}
+
+#endif

+ 9 - 0
src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj

@@ -22,6 +22,15 @@ Microsoft.AspNetCore.DataProtection.IDataProtector</Description>
     <Compile Include="$(SharedSourceRoot)CallerArgument\CallerArgumentExpressionAttribute.cs" LinkBase="Shared" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(TargetFramework)' != '$(DefaultNetCoreTargetFramework)'">
+    <Reference Include="System.Memory" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
+    <AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
+  </ItemGroup>
+
   <ItemGroup>
     <InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Abstractions.Tests" />
   </ItemGroup>

+ 0 - 0
src/DataProtection/Abstractions/src/PublicAPI.Shipped.txt → src/DataProtection/Abstractions/src/PublicAPI/net10.0/PublicAPI.Shipped.txt


+ 4 - 0
src/DataProtection/Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt

@@ -0,0 +1,4 @@
+#nullable enable
+Microsoft.AspNetCore.DataProtection.ISpanDataProtector
+Microsoft.AspNetCore.DataProtection.ISpanDataProtector.Protect<TWriter>(System.ReadOnlySpan<byte> plaintext, ref TWriter destination) -> void
+Microsoft.AspNetCore.DataProtection.ISpanDataProtector.Unprotect<TWriter>(System.ReadOnlySpan<byte> protectedData, ref TWriter destination) -> void

+ 16 - 0
src/DataProtection/Abstractions/src/PublicAPI/net462/PublicAPI.Shipped.txt

@@ -0,0 +1,16 @@
+#nullable enable
+Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider.CreateProtector(string! purpose) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+Microsoft.AspNetCore.DataProtection.IDataProtector
+Microsoft.AspNetCore.DataProtection.IDataProtector.Protect(byte[]! plaintext) -> byte[]!
+Microsoft.AspNetCore.DataProtection.IDataProtector.Unprotect(byte[]! protectedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator
+Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string?
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.CreateProtector(this Microsoft.AspNetCore.DataProtection.IDataProtectionProvider! provider, string! purpose, params string![]! subPurposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.CreateProtector(this Microsoft.AspNetCore.DataProtection.IDataProtectionProvider! provider, System.Collections.Generic.IEnumerable<string!>! purposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtectionProvider(this System.IServiceProvider! services) -> Microsoft.AspNetCore.DataProtection.IDataProtectionProvider!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtector(this System.IServiceProvider! services, string! purpose, params string![]! subPurposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtector(this System.IServiceProvider! services, System.Collections.Generic.IEnumerable<string!>! purposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Protect(this Microsoft.AspNetCore.DataProtection.IDataProtector! protector, string! plaintext) -> string!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Unprotect(this Microsoft.AspNetCore.DataProtection.IDataProtector! protector, string! protectedData) -> string!

+ 0 - 0
src/DataProtection/Abstractions/src/PublicAPI.Unshipped.txt → src/DataProtection/Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt


+ 16 - 0
src/DataProtection/Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt

@@ -0,0 +1,16 @@
+#nullable enable
+Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider.CreateProtector(string! purpose) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+Microsoft.AspNetCore.DataProtection.IDataProtector
+Microsoft.AspNetCore.DataProtection.IDataProtector.Protect(byte[]! plaintext) -> byte[]!
+Microsoft.AspNetCore.DataProtection.IDataProtector.Unprotect(byte[]! protectedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator
+Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string?
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.CreateProtector(this Microsoft.AspNetCore.DataProtection.IDataProtectionProvider! provider, string! purpose, params string![]! subPurposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.CreateProtector(this Microsoft.AspNetCore.DataProtection.IDataProtectionProvider! provider, System.Collections.Generic.IEnumerable<string!>! purposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtectionProvider(this System.IServiceProvider! services) -> Microsoft.AspNetCore.DataProtection.IDataProtectionProvider!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtector(this System.IServiceProvider! services, string! purpose, params string![]! subPurposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.GetDataProtector(this System.IServiceProvider! services, System.Collections.Generic.IEnumerable<string!>! purposes) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Protect(this Microsoft.AspNetCore.DataProtection.IDataProtector! protector, string! plaintext) -> string!
+static Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Unprotect(this Microsoft.AspNetCore.DataProtection.IDataProtector! protector, string! protectedData) -> string!

+ 0 - 0
src/DataProtection/DataProtection/src/PublicAPI.Unshipped.txt → src/DataProtection/Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt


+ 1 - 0
src/DataProtection/DataProtection.slnf

@@ -16,6 +16,7 @@
       "src\\DataProtection\\Extensions\\test\\Microsoft.AspNetCore.DataProtection.Extensions.Tests.csproj",
       "src\\DataProtection\\StackExchangeRedis\\src\\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj",
       "src\\DataProtection\\StackExchangeRedis\\test\\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Tests.csproj",
+      "src\\DataProtection\\benchmarks\\Microsoft.AspNetCore.DataProtection.MicroBenchmarks\\Microsoft.AspNetCore.DataProtection.MicroBenchmarks.csproj",
       "src\\DataProtection\\samples\\CustomEncryptorSample\\CustomEncryptorSample.csproj",
       "src\\DataProtection\\samples\\EntityFrameworkCoreSample\\EntityFrameworkCoreSample.csproj",
       "src\\DataProtection\\samples\\KeyManagementSample\\KeyManagementSample.csproj",

+ 60 - 0
src/DataProtection/DataProtection/src/AuthenticatedEncryption/ISpanAuthenticatedEncryptor.cs

@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NET
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+
+/// <summary>
+/// Provides an authenticated encryption and decryption routine via a span-based API.
+/// </summary>
+public interface ISpanAuthenticatedEncryptor : IAuthenticatedEncryptor
+{
+    /// <summary>
+    /// Encrypts and authenticates a piece of plaintext data and writes the result to a buffer writer.
+    /// </summary>
+    /// <typeparam name="TWriter">The type of buffer writer to write the ciphertext to.</typeparam>
+    /// <param name="plaintext">The plaintext to encrypt. This input may be zero bytes in length.</param>
+    /// <param name="additionalAuthenticatedData">
+    /// A piece of data which will not be included in the returned ciphertext
+    /// but which will still be covered by the authentication tag. This input may be zero bytes in length.
+    /// The same AAD must be specified in the corresponding call to <see cref="Decrypt{TWriter}"/>.
+    /// </param>
+    /// <param name="destination">The buffer writer to which the ciphertext (including authentication tag) will be written.</param>
+    /// <remarks>
+    /// This method provides an optimized, streaming alternative to <see cref="IAuthenticatedEncryptor.Encrypt(System.ArraySegment{byte}, System.ArraySegment{byte})"/>.
+    /// Rather than allocating an intermediate buffer, the ciphertext is written directly to the provided buffer writer,
+    /// which can improve performance and reduce memory allocation pressure.
+    /// The buffer writer is advanced by the total number of bytes written to it.
+    /// </remarks>
+    void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct;
+
+    /// <summary>
+    /// Validates the authentication tag of and decrypts a blob of encrypted data, writing the result to a buffer writer.
+    /// </summary>
+    /// <typeparam name="TWriter">The type of buffer writer to write the plaintext to.</typeparam>
+    /// <param name="ciphertext">The ciphertext (including authentication tag) to decrypt.</param>
+    /// <param name="additionalAuthenticatedData">
+    /// Any ancillary data which was used during computation of the authentication tag.
+    /// The same AAD must have been specified in the corresponding call to <see cref="Encrypt{TWriter}"/>.
+    /// </param>
+    /// <param name="destination">The buffer writer to which the decrypted plaintext will be written.</param>
+    /// <remarks>
+    /// This method provides an optimized, streaming alternative to <see cref="IAuthenticatedEncryptor.Decrypt(System.ArraySegment{byte}, System.ArraySegment{byte})"/>.
+    /// Rather than allocating an intermediate buffer, the plaintext is written directly to the provided buffer writer,
+    /// which can improve performance and reduce memory allocation pressure.
+    /// The buffer writer is advanced by the total number of bytes written to it.
+    /// </remarks>
+    void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct;
+}
+
+#endif

+ 400 - 233
src/DataProtection/DataProtection/src/Cng/CbcAuthenticatedEncryptor.cs

@@ -2,11 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
 using Microsoft.AspNetCore.Cryptography;
 using Microsoft.AspNetCore.Cryptography.Cng;
 using Microsoft.AspNetCore.Cryptography.SafeHandles;
 using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
-using Microsoft.AspNetCore.DataProtection.Cng.Internal;
 using Microsoft.AspNetCore.DataProtection.SP800_108;
 
 namespace Microsoft.AspNetCore.DataProtection.Cng;
@@ -14,7 +15,10 @@ namespace Microsoft.AspNetCore.DataProtection.Cng;
 // An encryptor which does Encrypt(CBC) + HMAC using the Windows CNG (BCrypt*) APIs.
 // The payloads produced by this encryptor should be compatible with the payloads
 // produced by the managed Encrypt(CBC) + HMAC encryptor.
-internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncryptorBase
+internal sealed unsafe class CbcAuthenticatedEncryptor : IOptimizedAuthenticatedEncryptor, IDisposable
+#if NET
+    , ISpanAuthenticatedEncryptor
+#endif
 {
     // Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single
     // key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block
@@ -56,222 +60,162 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
         _contextHeader = CreateContextHeader();
     }
 
-    private byte[] CreateContextHeader()
+    public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+        , allows ref struct
+#endif
     {
-        var retVal = new byte[checked(
-            1 /* KDF alg */
-            + 1 /* chaining mode */
-            + sizeof(uint) /* sym alg key size */
-            + sizeof(uint) /* sym alg block size */
-            + sizeof(uint) /* hmac alg key size */
-            + sizeof(uint) /* hmac alg digest size */
-            + _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
-            + _hmacAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
-
-        fixed (byte* pbRetVal = retVal)
+        // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
+        if (ciphertext.Length < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
         {
-            byte* ptr = pbRetVal;
-
-            // First is the two-byte header
-            *(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
-            *(ptr++) = 0; // 0x00 = CBC encryption + HMAC authentication
+            throw Error.CryptCommon_PayloadInvalid();
+        }
 
-            // Next is information about the symmetric algorithm (key size followed by block size)
-            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes);
-            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmBlockSizeInBytes);
+        var cbEncryptedDataLength = checked(ciphertext.Length - (int)(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
 
-            // Next is information about the HMAC algorithm (key size followed by digest size)
-            BitHelpers.WriteTo(ref ptr, _hmacAlgorithmSubkeyLengthInBytes);
-            BitHelpers.WriteTo(ref ptr, _hmacAlgorithmDigestLengthInBytes);
-
-            // See the design document for an explanation of the following code.
-            var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes];
-            fixed (byte* pbTempKeys = tempKeys)
+        // Assumption: ciphertext := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
+        fixed (byte* pbCiphertext = ciphertext)
+        fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
+        {
+            // Calculate offsets
+            byte* pbKeyModifier = pbCiphertext;
+            byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
+            byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
+            byte* pbActualHmac = &pbEncryptedData[cbEncryptedDataLength];
+
+            // Use the KDF to recreate the symmetric encryption and HMAC subkeys
+            // We'll need a temporary buffer to hold them
+            var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
+            byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
+            try
             {
-                byte dummy;
-
-                // Derive temporary keys for encryption + HMAC.
-                using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
-                {
-                    provider.DeriveKey(
-                        pbLabel: &dummy,
-                        cbLabel: 0,
-                        pbContext: &dummy,
-                        cbContext: 0,
-                        pbDerivedKey: pbTempKeys,
-                        cbDerivedKey: (uint)tempKeys.Length);
-                }
-
-                // At this point, tempKeys := { K_E || K_H }.
-                byte* pbSymmetricEncryptionSubkey = pbTempKeys;
-                byte* pbHmacSubkey = &pbTempKeys[_symmetricAlgorithmSubkeyLengthInBytes];
-
-                // Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
-                using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
+                _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
+                    pbLabel: pbAdditionalAuthenticatedData,
+                    cbLabel: (uint)additionalAuthenticatedData.Length,
+                    contextHeader: _contextHeader,
+                    pbContext: pbKeyModifier,
+                    cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
+                    pbDerivedKey: pbTempSubkeys,
+                    cbDerivedKey: cbTempSubkeys);
+
+                // Calculate offsets
+                byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
+                byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
+
+                // First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
+                // data hasn't been tampered with. The integrity check is also implicitly performed over
+                // keyModifier since that value was provided to the KDF earlier.
+                using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
                 {
-                    fixed (byte* pbIV = new byte[_symmetricAlgorithmBlockSizeInBytes] /* will be zero-initialized */)
+                    if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + (uint)cbEncryptedDataLength, pbActualHmac))
                     {
-                        DoCbcEncrypt(
-                            symmetricKeyHandle: symmetricKeyHandle,
-                            pbIV: pbIV,
-                            pbInput: &dummy,
-                            cbInput: 0,
-                            pbOutput: ptr,
-                            cbOutput: _symmetricAlgorithmBlockSizeInBytes);
+                        throw Error.CryptCommon_PayloadInvalid();
                     }
                 }
-                ptr += _symmetricAlgorithmBlockSizeInBytes;
 
-                // MAC a zero-length input string and copy the digest to the return buffer.
-                using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
+                // If the integrity check succeeded, decrypt the payload.
+                using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
                 {
-                    hashHandle.HashData(
-                        pbInput: &dummy,
-                        cbInput: 0,
-                        pbHashDigest: ptr,
-                        cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
+                    // BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
+                    byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
+                    UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
+
+                    // First, query the output size needed
+                    var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
+                        hKey: decryptionSubkeyHandle,
+                        pbInput: pbEncryptedData,
+                        cbInput: (uint)cbEncryptedDataLength,
+                        pPaddingInfo: null,
+                        pbIV: pbClonedIV,
+                        cbIV: _symmetricAlgorithmBlockSizeInBytes,
+                        pbOutput: null,  // NULL output = size query only
+                        cbOutput: 0,
+                        pcbResult: out var dwRequiredSize,
+                        dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
+                    UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+                    // Get buffer from writer with the required size
+                    var buffer = destination.GetSpan(checked((int)dwRequiredSize));
+
+                    // Clone IV again for the actual decryption call
+                    byte* pbClonedIV2 = stackalloc byte[(int)_symmetricAlgorithmBlockSizeInBytes];
+                    UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV2, byteCount: _symmetricAlgorithmBlockSizeInBytes);
+
+                    // Perform the actual decryption
+                    fixed (byte* pbBuffer = buffer)
+                    {
+                        byte dummy;
+                        ntstatus = UnsafeNativeMethods.BCryptDecrypt(
+                            hKey: decryptionSubkeyHandle,
+                            pbInput: pbEncryptedData,
+                            cbInput: (uint)cbEncryptedDataLength,
+                            pPaddingInfo: null,
+                            pbIV: pbClonedIV2,
+                            cbIV: _symmetricAlgorithmBlockSizeInBytes,
+                            pbOutput: (buffer.Length > 0) ? pbBuffer : &dummy,
+                            cbOutput: (uint)buffer.Length,
+                            pcbResult: out var dwActualDecryptedByteCount,
+                            dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
+
+                        UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+                        // Advance the writer by the number of bytes actually written
+                        destination.Advance(checked((int)dwActualDecryptedByteCount));
+                    }
                 }
-
-                ptr += _hmacAlgorithmDigestLengthInBytes;
-                CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
+            }
+            finally
+            {
+                // Buffer contains sensitive key material; delete.
+                UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
             }
         }
-
-        // retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || hmacAlgKeySize || hmacAlgDigestSize || E("") || MAC("") }.
-        return retVal;
     }
 
-    protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
+    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
     {
-        // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
-        if (cbCiphertext < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
+        ciphertext.Validate();
+        additionalAuthenticatedData.Validate();
+
+        var outputSize = ciphertext.Count - (int)(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes);
+        if (outputSize <= 0)
         {
             throw Error.CryptCommon_PayloadInvalid();
         }
 
-        // Assumption: pbCipherText := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
+#if NET
+        byte[]? rentedBuffer = null;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
 
-        var cbEncryptedData = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
-
-        // Calculate offsets
-        byte* pbKeyModifier = pbCiphertext;
-        byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
-        byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
-        byte* pbActualHmac = &pbEncryptedData[cbEncryptedData];
-
-        // Use the KDF to recreate the symmetric encryption and HMAC subkeys
-        // We'll need a temporary buffer to hold them
-        var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
-        byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
         try
         {
-            _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
-                pbLabel: pbAdditionalAuthenticatedData,
-                cbLabel: cbAdditionalAuthenticatedData,
-                contextHeader: _contextHeader,
-                pbContext: pbKeyModifier,
-                cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
-                pbDerivedKey: pbTempSubkeys,
-                cbDerivedKey: cbTempSubkeys);
-
-            // Calculate offsets
-            byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
-            byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
-
-            // First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
-            // data hasn't been tampered with. The integrity check is also implicitly performed over
-            // keyModifier since that value was provided to the KDF earlier.
-            using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
-            {
-                if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + cbEncryptedData, pbActualHmac))
-                {
-                    throw Error.CryptCommon_PayloadInvalid();
-                }
-            }
-
-            // If the integrity check succeeded, decrypt the payload.
-            using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
-            {
-                return DoCbcDecrypt(decryptionSubkeyHandle, pbIV, pbEncryptedData, cbEncryptedData);
-            }
+            Decrypt(ciphertext, additionalAuthenticatedData, ref refPooledBuffer);
+            return refPooledBuffer.WrittenSpan.ToArray();
         }
         finally
         {
-            // Buffer contains sensitive key material; delete.
-            UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
-        }
-    }
-
-    public override void Dispose()
-    {
-        _sp800_108_ctr_hmac_provider.Dispose();
-
-        // We don't want to dispose of the underlying algorithm instances because they
-        // might be reused.
-    }
-
-    // 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
-    private byte[] DoCbcDecrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte* pbInput, uint cbInput)
-    {
-        // BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
-        byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
-        UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
-
-        // First, figure out how large an output buffer we require.
-        // Ideally we'd be able to transform the last block ourselves and strip
-        // off the padding before creating the return value array, but we don't
-        // know the actual padding scheme being used under the covers (we can't
-        // assume PKCS#7). So unfortunately we're stuck with the temporary buffer.
-        // (Querying the output size won't mutate the IV.)
-        uint dwEstimatedDecryptedByteCount;
-        var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
-            hKey: symmetricKeyHandle,
-            pbInput: pbInput,
-            cbInput: cbInput,
-            pPaddingInfo: null,
-            pbIV: pbClonedIV,
-            cbIV: _symmetricAlgorithmBlockSizeInBytes,
-            pbOutput: null,
-            cbOutput: 0,
-            pcbResult: out dwEstimatedDecryptedByteCount,
-            dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
-        UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
-
-        var decryptedPayload = new byte[dwEstimatedDecryptedByteCount];
-        uint dwActualDecryptedByteCount;
-        fixed (byte* pbDecryptedPayload = decryptedPayload)
-        {
-            byte dummy;
-
-            // Perform the actual decryption.
-            ntstatus = UnsafeNativeMethods.BCryptDecrypt(
-                hKey: symmetricKeyHandle,
-                pbInput: pbInput,
-                cbInput: cbInput,
-                pPaddingInfo: null,
-                pbIV: pbClonedIV,
-                cbIV: _symmetricAlgorithmBlockSizeInBytes,
-                pbOutput: (pbDecryptedPayload != null) ? pbDecryptedPayload : &dummy, // CLR won't pin zero-length arrays
-                cbOutput: dwEstimatedDecryptedByteCount,
-                pcbResult: out dwActualDecryptedByteCount,
-                dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
-            UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
         }
 
-        // Decryption finished!
-        CryptoUtil.Assert(dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount, "dwActualDecryptedByteCount <= dwEstimatedDecryptedByteCount");
-        if (dwActualDecryptedByteCount == dwEstimatedDecryptedByteCount)
+#else
+        var pooledArrayBuffer = new PooledArrayBufferWriter<byte>(outputSize);
+        try
         {
-            // payload takes up the entire buffer
-            return decryptedPayload;
+            Decrypt(ciphertext, additionalAuthenticatedData, ref pooledArrayBuffer);
+            return pooledArrayBuffer.WrittenSpan.ToArray();
         }
-        else
+        finally
         {
-            // payload takes up only a partial buffer
-            var resizedDecryptedPayload = new byte[dwActualDecryptedByteCount];
-            Buffer.BlockCopy(decryptedPayload, 0, resizedDecryptedPayload, 0, resizedDecryptedPayload.Length);
-            return resizedDecryptedPayload;
+            pooledArrayBuffer.Dispose();
         }
+#endif
     }
 
     // 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
@@ -299,7 +243,16 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
         CryptoUtil.Assert(dwEncryptedBytes == cbOutput, "dwEncryptedBytes == cbOutput");
     }
 
-    protected override byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
+    public int GetEncryptedSize(int plainTextLength)
+    {
+        uint paddedCiphertextLength = GetCbcEncryptedOutputSizeWithPadding((uint)plainTextLength);
+        return checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + paddedCiphertextLength + _hmacAlgorithmDigestLengthInBytes));
+    }
+
+    public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+        , allows ref struct
+#endif
     {
         // This buffer will be used to hold the symmetric encryption and HMAC subkeys
         // used in the generation of this payload.
@@ -318,14 +271,17 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
             byte* pbIV = &pbKeyModifierAndIV[KEY_MODIFIER_SIZE_IN_BYTES];
 
             // Use the KDF to generate a new symmetric encryption and HMAC subkey
-            _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
-                pbLabel: pbAdditionalAuthenticatedData,
-                cbLabel: cbAdditionalAuthenticatedData,
-                contextHeader: _contextHeader,
-                pbContext: pbKeyModifier,
-                cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
-                pbDerivedKey: pbTempSubkeys,
-                cbDerivedKey: cbTempSubkeys);
+            fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
+            {
+                _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
+                    pbLabel: pbAdditionalAuthenticatedData,
+                    cbLabel: (uint)additionalAuthenticatedData.Length,
+                    contextHeader: _contextHeader,
+                    pbContext: pbKeyModifier,
+                    cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
+                    pbDerivedKey: pbTempSubkeys,
+                    cbDerivedKey: cbTempSubkeys);
+            }
 
             // Calculate offsets
             byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
@@ -333,50 +289,76 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
 
             using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
             {
-                // We can't assume PKCS#7 padding (maybe the underlying provider is really using CTS),
-                // so we need to query the padded output size before we can allocate the return value array.
-                var cbOutputCiphertext = GetCbcEncryptedOutputSizeWithPadding(symmetricKeyHandle, pbPlaintext, cbPlaintext);
-
-                // Allocate return value array and start copying some data
-                var retVal = new byte[checked(cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext + _hmacAlgorithmDigestLengthInBytes + cbPostBuffer)];
-                fixed (byte* pbRetVal = retVal)
+                // Query the padded ciphertext output size
+                byte dummy;
+                fixed (byte* pbPlaintextArray = plaintext)
                 {
-                    // Calculate offsets
-                    byte* pbOutputKeyModifier = &pbRetVal[cbPreBuffer];
-                    byte* pbOutputIV = &pbOutputKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
-                    byte* pbOutputCiphertext = &pbOutputIV[_symmetricAlgorithmBlockSizeInBytes];
-                    byte* pbOutputHmac = &pbOutputCiphertext[cbOutputCiphertext];
+                    var pbPlaintext = (pbPlaintextArray != null) ? pbPlaintextArray : &dummy;
 
-                    UnsafeBufferUtil.BlockCopy(from: pbKeyModifierAndIV, to: pbOutputKeyModifier, byteCount: cbKeyModifierAndIV);
-
-                    // retVal will eventually contain { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
-                    // At this point, retVal := { preBuffer | keyModifier | iv | _____ | _____ | postBuffer }
-
-                    DoCbcEncrypt(
-                        symmetricKeyHandle: symmetricKeyHandle,
-                        pbIV: pbIV,
+                    // First, query the size needed for ciphertext
+                    var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
+                        hKey: symmetricKeyHandle,
                         pbInput: pbPlaintext,
-                        cbInput: cbPlaintext,
-                        pbOutput: pbOutputCiphertext,
-                        cbOutput: cbOutputCiphertext);
+                        cbInput: (uint)plaintext.Length,
+                        pPaddingInfo: null,
+                        pbIV: pbIV,
+                        cbIV: _symmetricAlgorithmBlockSizeInBytes,
+                        pbOutput: null,  // NULL output = size query only
+                        cbOutput: 0,
+                        pcbResult: out var dwCiphertextSize,
+                        dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
+                    UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+                    // Calculate total required size
+                    var totalRequiredSize = checked((int)(cbKeyModifierAndIV + dwCiphertextSize + _hmacAlgorithmDigestLengthInBytes));
 
-                    // At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | _____ | postBuffer }
+                    // Get buffer from writer with the required total size
+                    var buffer = destination.GetSpan(totalRequiredSize);
 
-                    // Compute the HMAC over the IV and the ciphertext (prevents IV tampering).
-                    // The HMAC is already implicitly computed over the key modifier since the key
-                    // modifier is used as input to the KDF.
-                    using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
+                    fixed (byte* pbBuffer = buffer)
                     {
-                        hashHandle.HashData(
-                            pbInput: pbOutputIV,
-                            cbInput: checked(_symmetricAlgorithmBlockSizeInBytes + cbOutputCiphertext),
-                            pbHashDigest: pbOutputHmac,
-                            cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
+                        // Calculate offsets in destination buffer
+                        byte* pbOutputKeyModifier = pbBuffer;
+                        byte* pbOutputIV = &pbOutputKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
+                        byte* pbOutputCiphertext = &pbOutputIV[_symmetricAlgorithmBlockSizeInBytes];
+                        byte* pbOutputHmac = &pbOutputCiphertext[dwCiphertextSize];
+
+                        // Copy key modifier and IV to output
+                        Unsafe.CopyBlock(pbOutputKeyModifier, pbKeyModifierAndIV, cbKeyModifierAndIV);
+
+                        // Clone IV for encryption (BCryptEncrypt mutates it)
+                        byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
+                        UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
+
+                        // Perform encryption
+                        ntstatus = UnsafeNativeMethods.BCryptEncrypt(
+                            hKey: symmetricKeyHandle,
+                            pbInput: pbPlaintext,
+                            cbInput: (uint)plaintext.Length,
+                            pPaddingInfo: null,
+                            pbIV: pbClonedIV,
+                            cbIV: _symmetricAlgorithmBlockSizeInBytes,
+                            pbOutput: pbOutputCiphertext,
+                            cbOutput: dwCiphertextSize,
+                            pcbResult: out var dwActualCiphertextSize,
+                            dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
+                        UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+                        CryptoUtil.Assert(dwActualCiphertextSize == dwCiphertextSize, "dwActualCiphertextSize == dwCiphertextSize");
+
+                        // Calculate HMAC over (IV | ciphertext)
+                        using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
+                        {
+                            hashHandle.HashData(
+                                pbInput: pbOutputIV,
+                                cbInput: checked(_symmetricAlgorithmBlockSizeInBytes + dwCiphertextSize),
+                                pbHashDigest: pbOutputHmac,
+                                cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
+                        }
+
+                        // Advance the writer by the total bytes written
+                        destination.Advance(totalRequiredSize);
                     }
-
-                    // At this point, retVal := { preBuffer | keyModifier | iv | encryptedData | HMAC(iv | encryptedData) | postBuffer }
-                    // And we're done!
-                    return retVal;
                 }
             }
         }
@@ -387,6 +369,99 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
         }
     }
 
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
+        => Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
+
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
+    {
+        plaintext.Validate();
+        additionalAuthenticatedData.Validate();
+
+        var size = GetEncryptedSize(plaintext.Count);
+        var outputSize = (int)(preBufferSize + size + postBufferSize);
+
+#if NET
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
+        try
+        {
+            // arrays are pooled. and they MAY contain non-zeros in the pre-buffer and post-buffer regions.
+            // we could clean them up, but it's not strictly necessary - the important part is that output array
+            // has those pre/post buffer regions, which will be used by the caller.
+            refPooledBuffer.Advance((int)preBufferSize);
+            Encrypt(plaintext, additionalAuthenticatedData, ref refPooledBuffer);
+            refPooledBuffer.Advance((int)postBufferSize);
+
+            var resultSpan = refPooledBuffer.WrittenSpan.ToArray();
+            CryptoUtil.Assert(resultSpan.Length == outputSize, "writtenSpan length should equal calculated outputSize");
+            return resultSpan;
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+#else
+        var pooledArrayBuffer = new PooledArrayBufferWriter<byte>(outputSize);
+        try
+        {
+            pooledArrayBuffer.Advance(preBufferSize);
+            Encrypt(plaintext, additionalAuthenticatedData, ref pooledArrayBuffer);
+            pooledArrayBuffer.Advance(postBufferSize);
+
+            var resultSpan = pooledArrayBuffer.WrittenSpan.ToArray();
+            CryptoUtil.Assert(resultSpan.Length == outputSize, "writtenSpan length should equal calculated outputSize");
+
+            return resultSpan;
+        }
+        finally
+        {
+            pooledArrayBuffer.Dispose();
+        }
+#endif
+    }
+
+    /// <summary>
+    /// Should be used only for expected encrypt/decrypt size calculation,
+    /// use the other overload <see cref="GetCbcEncryptedOutputSizeWithPadding(BCryptKeyHandle, byte*, uint)"/>
+    /// for the actual encryption algorithm
+    /// </summary>
+    private uint GetCbcEncryptedOutputSizeWithPadding(uint cbInput)
+    {
+        // Create a temporary key with dummy data for size calculation only
+        // The actual key material doesn't matter for size calculation
+        byte* pbDummyKey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
+        // Leave pbDummyKey uninitialized (all zeros) - BCrypt doesn't care for size queries
+
+        using var tempKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbDummyKey, _symmetricAlgorithmSubkeyLengthInBytes);
+
+        // Use uninitialized IV and input data - only the lengths matter
+        byte* pbDummyIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
+        byte* pbDummyInput = stackalloc byte[checked((int)cbInput)];
+
+        var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
+            hKey: tempKeyHandle,
+            pbInput: pbDummyInput,
+            cbInput: cbInput,
+            pPaddingInfo: null,
+            pbIV: pbDummyIV,
+            cbIV: _symmetricAlgorithmBlockSizeInBytes,
+            pbOutput: null,  // NULL output = size query only
+            cbOutput: 0,
+            pcbResult: out var dwResult,
+            dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
+        UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+        return dwResult;
+    }
+
     private uint GetCbcEncryptedOutputSizeWithPadding(BCryptKeyHandle symmetricKeyHandle, byte* pbInput, uint cbInput)
     {
         // ok for this memory to remain uninitialized since nobody depends on it
@@ -394,7 +469,6 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
 
         // Calling BCryptEncrypt with a null output pointer will cause it to return the total number
         // of bytes required for the output buffer.
-        uint dwResult;
         var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
             hKey: symmetricKeyHandle,
             pbInput: pbInput,
@@ -404,7 +478,7 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
             cbIV: _symmetricAlgorithmBlockSizeInBytes,
             pbOutput: null,
             cbOutput: 0,
-            pcbResult: out dwResult,
+            pcbResult: out var dwResult,
             dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
         UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
 
@@ -418,4 +492,97 @@ internal sealed unsafe class CbcAuthenticatedEncryptor : CngAuthenticatedEncrypt
         hashHandle.HashData(pbInput, cbInput, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
         return CryptoUtil.TimeConstantBuffersAreEqual(pbExpectedDigest, pbActualDigest, _hmacAlgorithmDigestLengthInBytes);
     }
+
+    private byte[] CreateContextHeader()
+    {
+        var retVal = new byte[checked(
+            1 /* KDF alg */
+            + 1 /* chaining mode */
+            + sizeof(uint) /* sym alg key size */
+            + sizeof(uint) /* sym alg block size */
+            + sizeof(uint) /* hmac alg key size */
+            + sizeof(uint) /* hmac alg digest size */
+            + _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
+            + _hmacAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
+
+        fixed (byte* pbRetVal = retVal)
+        {
+            byte* ptr = pbRetVal;
+
+            // First is the two-byte header
+            *(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
+            *(ptr++) = 0; // 0x00 = CBC encryption + HMAC authentication
+
+            // Next is information about the symmetric algorithm (key size followed by block size)
+            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes);
+            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmBlockSizeInBytes);
+
+            // Next is information about the HMAC algorithm (key size followed by digest size)
+            BitHelpers.WriteTo(ref ptr, _hmacAlgorithmSubkeyLengthInBytes);
+            BitHelpers.WriteTo(ref ptr, _hmacAlgorithmDigestLengthInBytes);
+
+            // See the design document for an explanation of the following code.
+            var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes];
+            fixed (byte* pbTempKeys = tempKeys)
+            {
+                byte dummy;
+
+                // Derive temporary keys for encryption + HMAC.
+                using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
+                {
+                    provider.DeriveKey(
+                        pbLabel: &dummy,
+                        cbLabel: 0,
+                        pbContext: &dummy,
+                        cbContext: 0,
+                        pbDerivedKey: pbTempKeys,
+                        cbDerivedKey: (uint)tempKeys.Length);
+                }
+
+                // At this point, tempKeys := { K_E || K_H }.
+                byte* pbSymmetricEncryptionSubkey = pbTempKeys;
+                byte* pbHmacSubkey = &pbTempKeys[_symmetricAlgorithmSubkeyLengthInBytes];
+
+                // Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
+                using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
+                {
+                    fixed (byte* pbIV = new byte[_symmetricAlgorithmBlockSizeInBytes] /* will be zero-initialized */)
+                    {
+                        DoCbcEncrypt(
+                            symmetricKeyHandle: symmetricKeyHandle,
+                            pbIV: pbIV,
+                            pbInput: &dummy,
+                            cbInput: 0,
+                            pbOutput: ptr,
+                            cbOutput: _symmetricAlgorithmBlockSizeInBytes);
+                    }
+                }
+                ptr += _symmetricAlgorithmBlockSizeInBytes;
+
+                // MAC a zero-length input string and copy the digest to the return buffer.
+                using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
+                {
+                    hashHandle.HashData(
+                        pbInput: &dummy,
+                        cbInput: 0,
+                        pbHashDigest: ptr,
+                        cbHashDigest: _hmacAlgorithmDigestLengthInBytes);
+                }
+
+                ptr += _hmacAlgorithmDigestLengthInBytes;
+                CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
+            }
+        }
+
+        // retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || hmacAlgKeySize || hmacAlgDigestSize || E("") || MAC("") }.
+        return retVal;
+    }
+
+    public void Dispose()
+    {
+        _sp800_108_ctr_hmac_provider.Dispose();
+
+        // We don't want to dispose of the underlying algorithm instances because they
+        // might be reused.
+    }
 }

+ 281 - 147
src/DataProtection/DataProtection/src/Cng/CngGcmAuthenticatedEncryptor.cs

@@ -1,11 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System;
+using System.Buffers;
+using System.Security.Cryptography;
 using Microsoft.AspNetCore.Cryptography;
 using Microsoft.AspNetCore.Cryptography.Cng;
 using Microsoft.AspNetCore.Cryptography.SafeHandles;
 using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
-using Microsoft.AspNetCore.DataProtection.Cng.Internal;
 using Microsoft.AspNetCore.DataProtection.SP800_108;
 
 namespace Microsoft.AspNetCore.DataProtection.Cng;
@@ -20,7 +22,10 @@ namespace Microsoft.AspNetCore.DataProtection.Cng;
 // going to the IV. This means that we'll only hit the 2^-32 probability limit after 2^96 encryption
 // operations, which will realistically never happen. (At the absurd rate of one encryption operation
 // per nanosecond, it would still take 180 times the age of the universe to hit 2^96 operations.)
-internal sealed unsafe class CngGcmAuthenticatedEncryptor : CngAuthenticatedEncryptorBase
+internal sealed unsafe class CngGcmAuthenticatedEncryptor : IOptimizedAuthenticatedEncryptor, IDisposable
+#if NET
+    , ISpanAuthenticatedEncryptor
+#endif
 {
     // Having a key modifier ensures with overwhelming probability that no two encryption operations
     // will ever derive the same (encryption subkey, MAC subkey) pair. This limits an attacker's
@@ -50,90 +55,28 @@ internal sealed unsafe class CngGcmAuthenticatedEncryptor : CngAuthenticatedEncr
         _contextHeader = CreateContextHeader();
     }
 
-    private byte[] CreateContextHeader()
-    {
-        var retVal = new byte[checked(
-            1 /* KDF alg */
-            + 1 /* chaining mode */
-            + sizeof(uint) /* sym alg key size */
-            + sizeof(uint) /* GCM nonce size */
-            + sizeof(uint) /* sym alg block size */
-            + sizeof(uint) /* GCM tag size */
-            + TAG_SIZE_IN_BYTES /* tag of GCM-encrypted empty string */)];
-
-        fixed (byte* pbRetVal = retVal)
-        {
-            byte* ptr = pbRetVal;
-
-            // First is the two-byte header
-            *(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
-            *(ptr++) = 1; // 0x01 = GCM encryption + authentication
-
-            // Next is information about the symmetric algorithm (key size, nonce size, block size, tag size)
-            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes);
-            BitHelpers.WriteTo(ref ptr, NONCE_SIZE_IN_BYTES);
-            BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES); // block size = tag size
-            BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES);
-
-            // See the design document for an explanation of the following code.
-            var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
-            fixed (byte* pbTempKeys = tempKeys)
-            {
-                byte dummy;
-
-                // Derive temporary key for encryption.
-                using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
-                {
-                    provider.DeriveKey(
-                        pbLabel: &dummy,
-                        cbLabel: 0,
-                        pbContext: &dummy,
-                        cbContext: 0,
-                        pbDerivedKey: pbTempKeys,
-                        cbDerivedKey: (uint)tempKeys.Length);
-                }
-
-                // Encrypt a zero-length input string with an all-zero nonce and copy the tag to the return buffer.
-                byte* pbNonce = stackalloc byte[(int)NONCE_SIZE_IN_BYTES];
-                UnsafeBufferUtil.SecureZeroMemory(pbNonce, NONCE_SIZE_IN_BYTES);
-                DoGcmEncrypt(
-                    pbKey: pbTempKeys,
-                    cbKey: _symmetricAlgorithmSubkeyLengthInBytes,
-                    pbNonce: pbNonce,
-                    pbPlaintextData: &dummy,
-                    cbPlaintextData: 0,
-                    pbEncryptedData: &dummy,
-                    pbTag: ptr);
-            }
-
-            ptr += TAG_SIZE_IN_BYTES;
-            CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
-        }
-
-        // retVal := { version || chainingMode || symAlgKeySize || nonceSize || symAlgBlockSize || symAlgTagSize || TAG-of-E("") }.
-        return retVal;
-    }
-
-    protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
+    public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+        , allows ref struct
+#endif
     {
         // Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
-        if (cbCiphertext < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
+        if (ciphertext.Length < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
         {
             throw Error.CryptCommon_PayloadInvalid();
         }
 
-        // Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag }
-
-        var cbPlaintext = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES));
+        var plaintextLength = checked(ciphertext.Length - (int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES));
 
-        var retVal = new byte[cbPlaintext];
-        fixed (byte* pbRetVal = retVal)
+        // Assumption: ciphertext := { keyModifier || nonce || encryptedData || authenticationTag }
+        fixed (byte* pbCiphertext = ciphertext)
+        fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
         {
             // Calculate offsets
             byte* pbKeyModifier = pbCiphertext;
             byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
             byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
-            byte* pbAuthTag = &pbEncryptedData[cbPlaintext];
+            byte* pbAuthTag = &pbEncryptedData[plaintextLength];
 
             // Use the KDF to recreate the symmetric block cipher key
             // We'll need a temporary buffer to hold the symmetric encryption subkey
@@ -142,45 +85,50 @@ internal sealed unsafe class CngGcmAuthenticatedEncryptor : CngAuthenticatedEncr
             {
                 _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
                     pbLabel: pbAdditionalAuthenticatedData,
-                    cbLabel: cbAdditionalAuthenticatedData,
+                    cbLabel: (uint)additionalAuthenticatedData.Length,
                     contextHeader: _contextHeader,
                     pbContext: pbKeyModifier,
                     cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
                     pbDerivedKey: pbSymmetricDecryptionSubkey,
                     cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
 
+                // Get buffer from writer with the plaintext size
+                var buffer = destination.GetSpan(plaintextLength);
+
                 // Perform the decryption operation
                 using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
                 {
-                    byte dummy;
-                    byte* pbPlaintext = (pbRetVal != null) ? pbRetVal : &dummy; // CLR doesn't like pinning empty buffers
-
-                    BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
-                    BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo);
-                    authInfo.pbNonce = pbNonce;
-                    authInfo.cbNonce = NONCE_SIZE_IN_BYTES;
-                    authInfo.pbTag = pbAuthTag;
-                    authInfo.cbTag = TAG_SIZE_IN_BYTES;
-
-                    // The call to BCryptDecrypt will also validate the authentication tag
-                    uint cbDecryptedBytesWritten;
-                    var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
-                        hKey: decryptionSubkeyHandle,
-                        pbInput: pbEncryptedData,
-                        cbInput: cbPlaintext,
-                        pPaddingInfo: &authInfo,
-                        pbIV: null, // IV not used; nonce provided in pPaddingInfo
-                        cbIV: 0,
-                        pbOutput: pbPlaintext,
-                        cbOutput: cbPlaintext,
-                        pcbResult: out cbDecryptedBytesWritten,
-                        dwFlags: 0);
-                    UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
-                    CryptoUtil.Assert(cbDecryptedBytesWritten == cbPlaintext, "cbDecryptedBytesWritten == cbPlaintext");
-
-                    // At this point, retVal := { decryptedPayload }
-                    // And we're done!
-                    return retVal;
+                    fixed (byte* pbBuffer = buffer)
+                    {
+                        byte dummy;
+                        byte* pbPlaintext = (plaintextLength > 0) ? pbBuffer : &dummy; // CLR doesn't like pinning empty buffers
+
+                        BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
+                        BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo);
+                        authInfo.pbNonce = pbNonce;
+                        authInfo.cbNonce = NONCE_SIZE_IN_BYTES;
+                        authInfo.pbTag = pbAuthTag;
+                        authInfo.cbTag = TAG_SIZE_IN_BYTES;
+
+                        // The call to BCryptDecrypt will also validate the authentication tag
+                        uint cbDecryptedBytesWritten;
+                        var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
+                            hKey: decryptionSubkeyHandle,
+                            pbInput: pbEncryptedData,
+                            cbInput: (uint)plaintextLength,
+                            pPaddingInfo: &authInfo,
+                            pbIV: null, // IV not used; nonce provided in pPaddingInfo
+                            cbIV: 0,
+                            pbOutput: pbPlaintext,
+                            cbOutput: (uint)plaintextLength,
+                            pcbResult: out cbDecryptedBytesWritten,
+                            dwFlags: 0);
+                        UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+                        CryptoUtil.Assert(cbDecryptedBytesWritten == plaintextLength, "cbDecryptedBytesWritten == plaintextLength");
+
+                        // Advance the writer by the number of bytes written
+                        destination.Advance((int)cbDecryptedBytesWritten);
+                    }
                 }
             }
             finally
@@ -191,12 +139,49 @@ internal sealed unsafe class CngGcmAuthenticatedEncryptor : CngAuthenticatedEncr
         }
     }
 
-    public override void Dispose()
+    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
     {
-        _sp800_108_ctr_hmac_provider.Dispose();
+        ciphertext.Validate();
+        additionalAuthenticatedData.Validate();
 
-        // We don't want to dispose of the underlying algorithm instances because they
-        // might be reused.
+        var outputSize = ciphertext.Count - (int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES);
+        if (outputSize <= 0)
+        {
+            throw Error.CryptCommon_PayloadInvalid();
+        }
+
+#if NET
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
+        try
+        {
+            Decrypt(ciphertext, additionalAuthenticatedData, ref refPooledBuffer);
+            return refPooledBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+#else
+        var pooledArrayBuffer = new PooledArrayBufferWriter<byte>(outputSize);
+        try
+        {
+            Decrypt(ciphertext, additionalAuthenticatedData, ref pooledArrayBuffer);
+            return pooledArrayBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            pooledArrayBuffer.Dispose();
+        }
+#endif
     }
 
     // 'pbNonce' must point to a 96-bit buffer.
@@ -230,57 +215,206 @@ internal sealed unsafe class CngGcmAuthenticatedEncryptor : CngAuthenticatedEncr
         }
     }
 
-    protected override byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
+    public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+    , allows ref struct
+#endif
+    {
+        try
+        {
+            // Calculate total required size: keyModifier + nonce + plaintext + tag
+            // In GCM, ciphertext length equals plaintext length
+            var totalRequiredSize = checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Length + TAG_SIZE_IN_BYTES));
+
+            // Get buffer from writer with the required total size
+            var buffer = destination.GetSpan(totalRequiredSize);
+
+            fixed (byte* pbBuffer = buffer)
+            {
+                // Calculate offsets
+                byte* pbKeyModifier = pbBuffer;
+                byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
+                byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
+                byte* pbAuthTag = &pbEncryptedData[plaintext.Length];
+
+                // Randomly generate the key modifier and nonce
+                _genRandom.GenRandom(pbKeyModifier, KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES);
+
+                // Use the KDF to generate a new symmetric block cipher key
+                // We'll need a temporary buffer to hold the symmetric encryption subkey
+                byte* pbSymmetricEncryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
+                try
+                {
+                    fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
+                    {
+                        _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
+                            pbLabel: pbAdditionalAuthenticatedData,
+                            cbLabel: (uint)additionalAuthenticatedData.Length,
+                            contextHeader: _contextHeader,
+                            pbContext: pbKeyModifier,
+                            cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
+                            pbDerivedKey: pbSymmetricEncryptionSubkey,
+                            cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
+                    }
+
+                    // Perform the encryption operation
+                    byte dummy;
+                    fixed (byte* pbPlaintextArray = plaintext)
+                    {
+                        var pbPlaintext = (pbPlaintextArray != null) ? pbPlaintextArray : &dummy;
+
+                        DoGcmEncrypt(
+                            pbKey: pbSymmetricEncryptionSubkey,
+                            cbKey: _symmetricAlgorithmSubkeyLengthInBytes,
+                            pbNonce: pbNonce,
+                            pbPlaintextData: pbPlaintext,
+                            cbPlaintextData: (uint)plaintext.Length,
+                            pbEncryptedData: pbEncryptedData,
+                            pbTag: pbAuthTag);
+                    }
+
+                    // Advance the writer by the total bytes written
+                    destination.Advance(totalRequiredSize);
+                }
+                finally
+                {
+                    // The buffer contains key material, so delete it.
+                    UnsafeBufferUtil.SecureZeroMemory(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
+                }
+            }
+        }
+        catch (Exception ex) when (ex.RequiresHomogenization())
+        {
+            throw Error.CryptCommon_GenericError(ex);
+        }
+    }
+
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
+        => Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
+
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
+    {
+        plaintext.Validate();
+        additionalAuthenticatedData.Validate();
+
+        var size = checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES));
+        var outputSize = (int)(preBufferSize + size + postBufferSize);
+#if NET
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
+        try
+        {
+            // arrays are pooled. and they MAY contain non-zeros in the pre-buffer and post-buffer regions.
+            // we could clean them up, but it's not strictly necessary - the important part is that output array
+            // has those pre/post buffer regions, which will be used by the caller.
+            refPooledBuffer.Advance((int)preBufferSize);
+            Encrypt(plaintext, additionalAuthenticatedData, ref refPooledBuffer);
+            refPooledBuffer.Advance((int)postBufferSize);
+
+            var resultSpan = refPooledBuffer.WrittenSpan.ToArray();
+            CryptoUtil.Assert(resultSpan.Length == outputSize, "writtenSpan length should equal calculated outputSize");
+            return resultSpan;
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+#else
+        var pooledArrayBuffer = new PooledArrayBufferWriter<byte>(outputSize);
+        try
+        {
+            pooledArrayBuffer.Advance((int)preBufferSize);
+            Encrypt(plaintext, additionalAuthenticatedData, ref pooledArrayBuffer);
+            pooledArrayBuffer.Advance((int)postBufferSize);
+
+            var resultSpan = pooledArrayBuffer.WrittenSpan.ToArray();
+            CryptoUtil.Assert(resultSpan.Length == outputSize, "writtenSpan length should equal calculated outputSize");
+            return resultSpan;
+        }
+        finally
+        {
+            pooledArrayBuffer.Dispose();
+        }
+#endif
+    }
+
+    private byte[] CreateContextHeader()
     {
-        // Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag.
-        // In GCM, the encrypted output will be the same length as the plaintext input.
-        var retVal = new byte[checked(cbPreBuffer + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + cbPlaintext + TAG_SIZE_IN_BYTES + cbPostBuffer)];
+        var retVal = new byte[checked(
+            1 /* KDF alg */
+            + 1 /* chaining mode */
+            + sizeof(uint) /* sym alg key size */
+            + sizeof(uint) /* GCM nonce size */
+            + sizeof(uint) /* sym alg block size */
+            + sizeof(uint) /* GCM tag size */
+            + TAG_SIZE_IN_BYTES /* tag of GCM-encrypted empty string */)];
+
         fixed (byte* pbRetVal = retVal)
         {
-            // Calculate offsets
-            byte* pbKeyModifier = &pbRetVal[cbPreBuffer];
-            byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
-            byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
-            byte* pbAuthTag = &pbEncryptedData[cbPlaintext];
+            byte* ptr = pbRetVal;
 
-            // Randomly generate the key modifier and nonce
-            _genRandom.GenRandom(pbKeyModifier, KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES);
+            // First is the two-byte header
+            *(ptr++) = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
+            *(ptr++) = 1; // 0x01 = GCM encryption + authentication
 
-            // At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer }
+            // Next is information about the symmetric algorithm (key size, nonce size, block size, tag size)
+            BitHelpers.WriteTo(ref ptr, _symmetricAlgorithmSubkeyLengthInBytes);
+            BitHelpers.WriteTo(ref ptr, NONCE_SIZE_IN_BYTES);
+            BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES); // block size = tag size
+            BitHelpers.WriteTo(ref ptr, TAG_SIZE_IN_BYTES);
 
-            // Use the KDF to generate a new symmetric block cipher key
-            // We'll need a temporary buffer to hold the symmetric encryption subkey
-            byte* pbSymmetricEncryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
-            try
+            // See the design document for an explanation of the following code.
+            var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
+            fixed (byte* pbTempKeys = tempKeys)
             {
-                _sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
-                    pbLabel: pbAdditionalAuthenticatedData,
-                    cbLabel: cbAdditionalAuthenticatedData,
-                    contextHeader: _contextHeader,
-                    pbContext: pbKeyModifier,
-                    cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
-                    pbDerivedKey: pbSymmetricEncryptionSubkey,
-                    cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
+                byte dummy;
+
+                // Derive temporary key for encryption.
+                using (var provider = SP800_108_CTR_HMACSHA512Util.CreateEmptyProvider())
+                {
+                    provider.DeriveKey(
+                        pbLabel: &dummy,
+                        cbLabel: 0,
+                        pbContext: &dummy,
+                        cbContext: 0,
+                        pbDerivedKey: pbTempKeys,
+                        cbDerivedKey: (uint)tempKeys.Length);
+                }
 
-                // Perform the encryption operation
+                // Encrypt a zero-length input string with an all-zero nonce and copy the tag to the return buffer.
+                byte* pbNonce = stackalloc byte[(int)NONCE_SIZE_IN_BYTES];
+                UnsafeBufferUtil.SecureZeroMemory(pbNonce, NONCE_SIZE_IN_BYTES);
                 DoGcmEncrypt(
-                    pbKey: pbSymmetricEncryptionSubkey,
+                    pbKey: pbTempKeys,
                     cbKey: _symmetricAlgorithmSubkeyLengthInBytes,
                     pbNonce: pbNonce,
-                    pbPlaintextData: pbPlaintext,
-                    cbPlaintextData: cbPlaintext,
-                    pbEncryptedData: pbEncryptedData,
-                    pbTag: pbAuthTag);
-
-                // At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer }
-                // And we're done!
-                return retVal;
-            }
-            finally
-            {
-                // The buffer contains key material, so delete it.
-                UnsafeBufferUtil.SecureZeroMemory(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
+                    pbPlaintextData: &dummy,
+                    cbPlaintextData: 0,
+                    pbEncryptedData: &dummy,
+                    pbTag: ptr);
             }
+
+            ptr += TAG_SIZE_IN_BYTES;
+            CryptoUtil.Assert(ptr - pbRetVal == retVal.Length, "ptr - pbRetVal == retVal.Length");
         }
+
+        // retVal := { version || chainingMode || symAlgKeySize || nonceSize || symAlgBlockSize || symAlgTagSize || TAG-of-E("") }.
+        return retVal;
+    }
+
+    public void Dispose()
+    {
+        _sp800_108_ctr_hmac_provider.Dispose();
+
+        // We don't want to dispose of the underlying algorithm instances because they
+        // might be reused.
     }
 }

+ 0 - 86
src/DataProtection/DataProtection/src/Cng/Internal/CngAuthenticatedEncryptorBase.cs

@@ -1,86 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
-
-namespace Microsoft.AspNetCore.DataProtection.Cng.Internal;
-
-/// <summary>
-/// Base class used for all CNG-related authentication encryption operations.
-/// </summary>
-internal abstract unsafe class CngAuthenticatedEncryptorBase : IOptimizedAuthenticatedEncryptor, IDisposable
-{
-    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
-    {
-        // This wrapper simply converts ArraySegment<byte> to byte* and calls the impl method.
-
-        // Input validation
-        ciphertext.Validate();
-        additionalAuthenticatedData.Validate();
-
-        byte dummy; // used only if plaintext or AAD is empty, since otherwise 'fixed' returns null pointer
-        fixed (byte* pbCiphertextArray = ciphertext.Array)
-        {
-            fixed (byte* pbAdditionalAuthenticatedDataArray = additionalAuthenticatedData.Array)
-            {
-                try
-                {
-                    return DecryptImpl(
-                        pbCiphertext: (pbCiphertextArray != null) ? &pbCiphertextArray[ciphertext.Offset] : &dummy,
-                        cbCiphertext: (uint)ciphertext.Count,
-                        pbAdditionalAuthenticatedData: (pbAdditionalAuthenticatedDataArray != null) ? &pbAdditionalAuthenticatedDataArray[additionalAuthenticatedData.Offset] : &dummy,
-                        cbAdditionalAuthenticatedData: (uint)additionalAuthenticatedData.Count);
-                }
-                catch (Exception ex) when (ex.RequiresHomogenization())
-                {
-                    // Homogenize to CryptographicException.
-                    throw Error.CryptCommon_GenericError(ex);
-                }
-            }
-        }
-    }
-
-    protected abstract byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData);
-
-    public abstract void Dispose();
-
-    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
-    {
-        return Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
-    }
-
-    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
-    {
-        // This wrapper simply converts ArraySegment<byte> to byte* and calls the impl method.
-
-        // Input validation
-        plaintext.Validate();
-        additionalAuthenticatedData.Validate();
-
-        byte dummy; // used only if plaintext or AAD is empty, since otherwise 'fixed' returns null pointer
-        fixed (byte* pbPlaintextArray = plaintext.Array)
-        {
-            fixed (byte* pbAdditionalAuthenticatedDataArray = additionalAuthenticatedData.Array)
-            {
-                try
-                {
-                    return EncryptImpl(
-                        pbPlaintext: (pbPlaintextArray != null) ? &pbPlaintextArray[plaintext.Offset] : &dummy,
-                        cbPlaintext: (uint)plaintext.Count,
-                        pbAdditionalAuthenticatedData: (pbAdditionalAuthenticatedDataArray != null) ? &pbAdditionalAuthenticatedDataArray[additionalAuthenticatedData.Offset] : &dummy,
-                        cbAdditionalAuthenticatedData: (uint)additionalAuthenticatedData.Count,
-                        cbPreBuffer: preBufferSize,
-                        cbPostBuffer: postBufferSize);
-                }
-                catch (Exception ex) when (ex.RequiresHomogenization())
-                {
-                    // Homogenize to CryptographicException.
-                    throw Error.CryptCommon_GenericError(ex);
-                }
-            }
-        }
-    }
-
-    protected abstract byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer);
-}

+ 17 - 0
src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtectionProvider.cs

@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
 using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
 using Microsoft.AspNetCore.Shared;
 using Microsoft.Extensions.Logging;
@@ -23,6 +24,22 @@ internal sealed unsafe class KeyRingBasedDataProtectionProvider : IDataProtectio
     {
         ArgumentNullThrowHelper.ThrowIfNull(purpose);
 
+        var currentKeyRing = _keyRingProvider.GetCurrentKeyRing();
+        var encryptor = currentKeyRing.DefaultAuthenticatedEncryptor;
+
+#if NET
+        if (encryptor is ISpanAuthenticatedEncryptor)
+        {
+            // allows caller to check if dataProtector supports Span APIs
+            // and use more performant APIs
+            return new KeyRingBasedSpanDataProtector(
+                logger: _logger,
+                keyRingProvider: _keyRingProvider,
+                originalPurposes: null,
+                newPurpose: purpose);
+        }
+#endif
+
         return new KeyRingBasedDataProtector(
             logger: _logger,
             keyRingProvider: _keyRingProvider,

+ 43 - 37
src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtector.cs

@@ -21,18 +21,19 @@ using Microsoft.AspNetCore.DataProtection.Internal;
 
 namespace Microsoft.AspNetCore.DataProtection.KeyManagement;
 
-internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersistedDataProtector
+internal unsafe class KeyRingBasedDataProtector : IDataProtector, IPersistedDataProtector
 {
     // This magic header identifies a v0 protected data blob. It's the high 28 bits of the SHA1 hash of
     // "Microsoft.AspNet.DataProtection.KeyManagement.KeyRingBasedDataProtector" [US-ASCII], big-endian.
     // The last nibble reserved for version information. There's also the nice property that "F0 C9"
     // can never appear in a well-formed UTF8 sequence, so attempts to treat a protected payload as a
     // UTF8-encoded string will fail, and devs can catch the mistake early.
-    private const uint MAGIC_HEADER_V0 = 0x09F0C9F0;
+    protected const uint MAGIC_HEADER_V0 = 0x09F0C9F0;
+    protected static readonly int _magicHeaderKeyIdSize = sizeof(uint) + sizeof(Guid);
 
-    private AdditionalAuthenticatedDataTemplate _aadTemplate;
-    private readonly IKeyRingProvider _keyRingProvider;
-    private readonly ILogger? _logger;
+    protected AdditionalAuthenticatedDataTemplate _aadTemplate;
+    protected readonly IKeyRingProvider _keyRingProvider;
+    protected readonly ILogger? _logger;
 
     public KeyRingBasedDataProtector(IKeyRingProvider keyRingProvider, ILogger? logger, string[]? originalPurposes, string newPurpose)
     {
@@ -65,6 +66,22 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
     {
         ArgumentNullThrowHelper.ThrowIfNull(purpose);
 
+        var currentKeyRing = _keyRingProvider.GetCurrentKeyRing();
+        var encryptor = currentKeyRing.DefaultAuthenticatedEncryptor;
+
+#if NET
+        if (encryptor is ISpanAuthenticatedEncryptor)
+        {
+            // allows caller to check if dataProtector supports Span APIs
+            // and use more performant APIs
+            return new KeyRingBasedSpanDataProtector(
+                logger: _logger,
+                keyRingProvider: _keyRingProvider,
+                originalPurposes: Purposes,
+                newPurpose: purpose);
+        }
+#endif
+
         return new KeyRingBasedDataProtector(
             logger: _logger,
             keyRingProvider: _keyRingProvider,
@@ -72,24 +89,11 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
             newPurpose: purpose);
     }
 
-    private static string JoinPurposesForLog(IEnumerable<string> purposes)
+    protected static string JoinPurposesForLog(IEnumerable<string> purposes)
     {
         return "(" + String.Join(", ", purposes.Select(p => "'" + p + "'")) + ")";
     }
 
-    // allows decrypting payloads whose keys have been revoked
-    public byte[] DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors, out bool requiresMigration, out bool wasRevoked)
-    {
-        // argument & state checking
-        ArgumentNullThrowHelper.ThrowIfNull(protectedData);
-
-        UnprotectStatus status;
-        var retVal = UnprotectCore(protectedData, ignoreRevocationErrors, status: out status);
-        requiresMigration = (status != UnprotectStatus.Ok);
-        wasRevoked = (status == UnprotectStatus.DecryptionKeyWasRevoked);
-        return retVal;
-    }
-
     public byte[] Protect(byte[] plaintext)
     {
         ArgumentNullThrowHelper.ThrowIfNull(plaintext);
@@ -140,7 +144,7 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
         }
     }
 
-    private static Guid ReadGuid(void* ptr)
+    protected static Guid ReadGuid(void* ptr)
     {
 #if NETCOREAPP
         // Performs appropriate endianness fixups
@@ -153,15 +157,7 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
 #endif
     }
 
-    private static uint ReadBigEndian32BitInteger(byte* ptr)
-    {
-        return ((uint)ptr[0] << 24)
-            | ((uint)ptr[1] << 16)
-            | ((uint)ptr[2] << 8)
-            | ((uint)ptr[3]);
-    }
-
-    private static bool TryGetVersionFromMagicHeader(uint magicHeader, out int version)
+    protected static bool TryGetVersionFromMagicHeader(uint magicHeader, out int version)
     {
         const uint MAGIC_HEADER_VERSION_MASK = 0xFU;
         if ((magicHeader & ~MAGIC_HEADER_VERSION_MASK) == MAGIC_HEADER_V0)
@@ -187,14 +183,26 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
             wasRevoked: out _);
     }
 
+    // allows decrypting payloads whose keys have been revoked
+    public byte[] DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors, out bool requiresMigration, out bool wasRevoked)
+    {
+        // argument & state checking
+        ArgumentNullThrowHelper.ThrowIfNull(protectedData);
+
+        UnprotectStatus status;
+        var retVal = UnprotectCore(protectedData, ignoreRevocationErrors, status: out status);
+        requiresMigration = (status != UnprotectStatus.Ok);
+        wasRevoked = (status == UnprotectStatus.DecryptionKeyWasRevoked);
+        return retVal;
+    }
+
     private byte[] UnprotectCore(byte[] protectedData, bool allowOperationsOnRevokedKeys, out UnprotectStatus status)
     {
         Debug.Assert(protectedData != null);
 
         try
         {
-            // argument & state checking
-            if (protectedData.Length < sizeof(uint) /* magic header */ + sizeof(Guid) /* key id */)
+            if (protectedData.Length < _magicHeaderKeyIdSize)
             {
                 // payload must contain at least the magic header and key id
                 throw Error.ProtectionProvider_BadMagicHeader();
@@ -203,17 +211,15 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
             // Need to check that protectedData := { magicHeader || keyId || encryptorSpecificProtectedPayload }
 
             // Parse the payload version number and key id.
-            uint magicHeaderFromPayload;
+            var magicHeaderFromPayload = BinaryPrimitives.ReadUInt32BigEndian(protectedData.AsSpan(0, sizeof(uint)));
             Guid keyIdFromPayload;
             fixed (byte* pbInput = protectedData)
             {
-                magicHeaderFromPayload = ReadBigEndian32BitInteger(pbInput);
                 keyIdFromPayload = ReadGuid(&pbInput[sizeof(uint)]);
             }
 
             // Are the magic header and version information correct?
-            int payloadVersion;
-            if (!TryGetVersionFromMagicHeader(magicHeaderFromPayload, out payloadVersion))
+            if (!TryGetVersionFromMagicHeader(magicHeaderFromPayload, out var payloadVersion))
             {
                 throw Error.ProtectionProvider_BadMagicHeader();
             }
@@ -293,7 +299,7 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
         }
     }
 
-    private static void WriteGuid(void* ptr, Guid value)
+    protected static void WriteGuid(void* ptr, Guid value)
     {
 #if NETCOREAPP
         var span = new Span<byte>(ptr, sizeof(Guid));
@@ -309,7 +315,7 @@ internal sealed unsafe class KeyRingBasedDataProtector : IDataProtector, IPersis
 #endif
     }
 
-    private static void WriteBigEndianInteger(byte* ptr, uint value)
+    protected static void WriteBigEndianInteger(byte* ptr, uint value)
     {
         ptr[0] = (byte)(value >> 24);
         ptr[1] = (byte)(value >> 16);

+ 170 - 0
src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedSpanDataProtector.cs

@@ -0,0 +1,170 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NET
+
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using Microsoft.AspNetCore.Cryptography;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
+using Microsoft.AspNetCore.Shared;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.DataProtection.KeyManagement;
+
+internal unsafe class KeyRingBasedSpanDataProtector : KeyRingBasedDataProtector, ISpanDataProtector
+{
+    public KeyRingBasedSpanDataProtector(IKeyRingProvider keyRingProvider, ILogger? logger, string[]? originalPurposes, string newPurpose)
+        : base(keyRingProvider, logger, originalPurposes, newPurpose)
+    {
+    }
+
+    public void Protect<TWriter>(ReadOnlySpan<byte> plaintext, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+        , allows ref struct
+#endif
+    {
+        try
+        {
+            // Perform the encryption operation using the current default encryptor.
+            var currentKeyRing = _keyRingProvider.GetCurrentKeyRing();
+            var defaultKeyId = currentKeyRing.DefaultKeyId;
+            var defaultEncryptor = (ISpanAuthenticatedEncryptor)currentKeyRing.DefaultAuthenticatedEncryptor!;
+            CryptoUtil.Assert(defaultEncryptor != null, "DefaultAuthenticatedEncryptor != null");
+
+            if (_logger.IsDebugLevelEnabled())
+            {
+                _logger.PerformingProtectOperationToKeyWithPurposes(defaultKeyId, JoinPurposesForLog(Purposes));
+            }
+
+            // We'll need to apply the default key id to the template if it hasn't already been applied.
+            // If the default key id has been updated since the last call to Protect, also write back the updated template.
+            var aad = _aadTemplate.GetAadForKey(defaultKeyId, isProtecting: true);
+            var preBufferSize = _magicHeaderKeyIdSize;
+
+            // Step 1: Write the magic header and key id
+            var headerBuffer = destination.GetSpan(preBufferSize);
+#if NET
+            BinaryPrimitives.WriteUInt32BigEndian(headerBuffer.Slice(0, sizeof(uint)), MAGIC_HEADER_V0);
+            var writeKeyIdResult = defaultKeyId.TryWriteBytes(headerBuffer.Slice(sizeof(uint), sizeof(Guid)));
+            Debug.Assert(writeKeyIdResult, "Failed to write Guid to destination.");
+#else
+            fixed (byte* pbBuffer = headerBuffer)
+            {
+                WriteBigEndianInteger(pbBuffer, MAGIC_HEADER_V0);
+                WriteGuid(&pbBuffer[sizeof(uint)], defaultKeyId);
+            }
+#endif
+            destination.Advance(preBufferSize);
+
+            // Step 2: Perform encryption into the destination writer
+            defaultEncryptor.Encrypt(plaintext, aad, ref destination);
+
+            // At this point, destination := { magicHeader || keyId || encryptorSpecificProtectedPayload }
+            // And we're done!
+        }
+        catch (Exception ex) when (ex.RequiresHomogenization())
+        {
+            // homogenize all errors to CryptographicException
+            throw Error.Common_EncryptionFailed(ex);
+        }
+    }
+
+    public void Unprotect<TWriter>(ReadOnlySpan<byte> protectedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
+#if NET
+        , allows ref struct
+#endif
+    {
+        try
+        {
+            if (protectedData.Length < _magicHeaderKeyIdSize)
+            {
+                // payload must contain at least the magic header and key id
+                throw Error.ProtectionProvider_BadMagicHeader();
+            }
+
+            // Parse the payload version number and key id.
+            var magicHeaderFromPayload = BinaryPrimitives.ReadUInt32BigEndian(protectedData.Slice(0, sizeof(uint)));
+#if NET
+            var keyIdFromPayload = new Guid(protectedData.Slice(sizeof(uint), sizeof(Guid)));
+#else
+            Guid keyIdFromPayload;
+            fixed (byte* pbProtectedData = protectedData)
+            {
+                keyIdFromPayload = ReadGuid(&pbProtectedData[sizeof(uint)]);
+            }
+#endif
+
+            // Are the magic header and version information correct?
+            if (!TryGetVersionFromMagicHeader(magicHeaderFromPayload, out var payloadVersion))
+            {
+                throw Error.ProtectionProvider_BadMagicHeader();
+            }
+
+            if (payloadVersion != 0)
+            {
+                throw Error.ProtectionProvider_BadVersion();
+            }
+
+            if (_logger.IsDebugLevelEnabled())
+            {
+                _logger.PerformingUnprotectOperationToKeyWithPurposes(keyIdFromPayload, JoinPurposesForLog(Purposes));
+            }
+
+            // Find the correct encryptor in the keyring.
+            var currentKeyRing = _keyRingProvider.GetCurrentKeyRing();
+            var requestedEncryptor = currentKeyRing.GetAuthenticatedEncryptorByKeyId(keyIdFromPayload, out bool keyWasRevoked);
+            if (requestedEncryptor is null)
+            {
+                if (_keyRingProvider is KeyRingProvider provider && provider.InAutoRefreshWindow())
+                {
+                    currentKeyRing = provider.RefreshCurrentKeyRing();
+                    requestedEncryptor = currentKeyRing.GetAuthenticatedEncryptorByKeyId(keyIdFromPayload, out keyWasRevoked);
+                }
+
+                if (requestedEncryptor is null)
+                {
+                    if (_logger.IsTraceLevelEnabled())
+                    {
+                        _logger.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed(keyIdFromPayload);
+                    }
+
+                    throw Error.Common_KeyNotFound(keyIdFromPayload);
+                }
+            }
+
+            // Check if key was revoked - for simplified version, we disallow revoked keys
+            if (keyWasRevoked)
+            {
+                if (_logger.IsDebugLevelEnabled())
+                {
+                    _logger.KeyWasRevokedUnprotectOperationCannotProceed(keyIdFromPayload);
+                }
+
+                throw Error.Common_KeyRevoked(keyIdFromPayload);
+            }
+
+            // Perform the decryption operation.
+            ReadOnlySpan<byte> actualCiphertext = protectedData.Slice(sizeof(uint) + sizeof(Guid)); // chop off magic header + key id
+            ReadOnlySpan<byte> aad = _aadTemplate.GetAadForKey(keyIdFromPayload, isProtecting: false);
+
+            // At this point, actualCiphertext := { encryptorSpecificPayload },
+            // so all that's left is to invoke the decryption routine directly.
+            var spanEncryptor = (ISpanAuthenticatedEncryptor)requestedEncryptor;
+            spanEncryptor.Decrypt(actualCiphertext, aad, ref destination);
+
+            // At this point, destination contains the decrypted plaintext
+            // And we're done!
+        }
+        catch (Exception ex) when (ex.RequiresHomogenization())
+        {
+            // homogenize all errors to CryptographicException
+            throw Error.DecryptionFailed(ex);
+        }
+    }
+}
+
+#endif

+ 142 - 76
src/DataProtection/DataProtection/src/Managed/AesGcmAuthenticatedEncryptor.cs

@@ -3,6 +3,7 @@
 
 #if NETCOREAPP
 using System;
+using System.Buffers;
 using System.IO;
 using System.Security.Cryptography;
 using Microsoft.AspNetCore.Cryptography;
@@ -13,7 +14,7 @@ using Microsoft.AspNetCore.DataProtection.SP800_108;
 namespace Microsoft.AspNetCore.DataProtection.Managed;
 
 // An encryptor that uses AesGcm to do encryption
-internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthenticatedEncryptor, IDisposable
+internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthenticatedEncryptor, ISpanAuthenticatedEncryptor, IDisposable
 {
     // Having a key modifier ensures with overwhelming probability that no two encryption operations
     // will ever derive the same (encryption subkey, MAC subkey) pair. This limits an attacker's
@@ -64,73 +65,73 @@ internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthentica
         _genRandom = genRandom ?? ManagedGenRandomImpl.Instance;
     }
 
-    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
+    public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct
     {
-        ciphertext.Validate();
-        additionalAuthenticatedData.Validate();
-
-        // Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
-        if (ciphertext.Count < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
+        try
         {
-            throw Error.CryptCommon_PayloadInvalid();
-        }
+            // Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
+            if (ciphertext.Length < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
+            {
+                throw Error.CryptCommon_PayloadInvalid();
+            }
 
-        // Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag }
-        var plaintextBytes = ciphertext.Count - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES);
-        var plaintext = new byte[plaintextBytes];
+            var plaintextBytes = ciphertext.Length - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES);
 
-        try
-        {
-            // Step 1: Extract the key modifier from the payload.
+            // Calculate offsets in the ciphertext
+            var keyModifierOffset = 0;
+            var nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
+            var encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES;
+            var tagOffset = encryptedDataOffset + plaintextBytes;
 
-            int keyModifierOffset; // position in ciphertext.Array where key modifier begins
-            int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins
-            int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins
-            int tagOffset; // position in ciphertext.Array where encrypted data ends
+            // Extract spans for each component
+            var keyModifier = ciphertext.Slice(keyModifierOffset, KEY_MODIFIER_SIZE_IN_BYTES);
+            var nonce = ciphertext.Slice(nonceOffset, NONCE_SIZE_IN_BYTES);
+            var encrypted = ciphertext.Slice(encryptedDataOffset, plaintextBytes);
+            var tag = ciphertext.Slice(tagOffset, TAG_SIZE_IN_BYTES);
 
-            checked
-            {
-                keyModifierOffset = ciphertext.Offset;
-                nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
-                encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES;
-                tagOffset = encryptedDataOffset + plaintextBytes;
-            }
+            // Get buffer from writer with the plaintext size
+            var buffer = destination.GetSpan(plaintextBytes);
 
-            var keyModifier = new ArraySegment<byte>(ciphertext.Array!, keyModifierOffset, KEY_MODIFIER_SIZE_IN_BYTES);
+            // Get the plaintext destination
+            var plaintext = buffer.Slice(0, plaintextBytes);
 
-            // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
-            // We pin all unencrypted keys to limit their exposure via GC relocation.
+            // Decrypt the KDK and use it to restore the original encryption key
+            // We pin all unencrypted keys to limit their exposure via GC relocation
+            Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
+                ? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
+                : new byte[_keyDerivationKey.Length];
 
-            var decryptedKdk = new byte[_keyDerivationKey.Length];
-            var derivedKey = new byte[_derivedkeySizeInBytes];
+            Span<byte> derivedKey = _derivedkeySizeInBytes <= 256
+                ? stackalloc byte[256].Slice(0, _derivedkeySizeInBytes)
+                : new byte[_derivedkeySizeInBytes];
 
-            fixed (byte* __unused__1 = decryptedKdk)
-            fixed (byte* __unused__2 = derivedKey)
+            fixed (byte* decryptedKdkUnsafe = decryptedKdk)
+            fixed (byte* derivedKeyUnsafe = derivedKey)
             {
                 try
                 {
-                    _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
+                    _keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
                     ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
                         kdk: decryptedKdk,
                         label: additionalAuthenticatedData,
                         contextHeader: _contextHeader,
                         contextData: keyModifier,
                         operationSubkey: derivedKey,
-                        validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
+                        validationSubkey: Span<byte>.Empty /* filling in derivedKey only */);
 
-                    // Perform the decryption operation
-                    var nonce = new Span<byte>(ciphertext.Array, nonceOffset, NONCE_SIZE_IN_BYTES);
-                    var tag = new Span<byte>(ciphertext.Array, tagOffset, TAG_SIZE_IN_BYTES);
-                    var encrypted = new Span<byte>(ciphertext.Array, encryptedDataOffset, plaintextBytes);
+                    // Perform the decryption operation directly into destination
                     using var aes = new AesGcm(derivedKey, TAG_SIZE_IN_BYTES);
                     aes.Decrypt(nonce, encrypted, tag, plaintext);
-                    return plaintext;
+
+                    // Advance the writer by the number of bytes written
+                    destination.Advance(plaintextBytes);
                 }
                 finally
                 {
                     // delete since these contain secret material
-                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
-                    Array.Clear(derivedKey, 0, derivedKey.Length);
+                    decryptedKdk.Clear();
+                    derivedKey.Clear();
                 }
             }
         }
@@ -141,72 +142,141 @@ internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthentica
         }
     }
 
+    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
+    {
+        ciphertext.Validate();
+        additionalAuthenticatedData.Validate();
+
+        var outputSize = ciphertext.Count - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES);
+        if (outputSize < 0)
+        {
+            throw Error.CryptCommon_PayloadInvalid();
+        }
+
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
+        try
+        {
+            Decrypt(ciphertext, additionalAuthenticatedData, ref refPooledBuffer);
+            return refPooledBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+    }
+
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
+        => Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
+
     public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData, uint preBufferSize, uint postBufferSize)
     {
         plaintext.Validate();
         additionalAuthenticatedData.Validate();
 
+        var size = checked(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES);
+        var outputSize = (int)(preBufferSize + size + postBufferSize);
+
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
         try
         {
-            // Allocate a buffer to hold the key modifier, nonce, encrypted data, and tag.
-            // In GCM, the encrypted output will be the same length as the plaintext input.
-            var retVal = new byte[checked(preBufferSize + KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Count + TAG_SIZE_IN_BYTES + postBufferSize)];
-            int keyModifierOffset; // position in ciphertext.Array where key modifier begins
-            int nonceOffset; // position in ciphertext.Array where key modifier ends / nonce begins
-            int encryptedDataOffset; // position in ciphertext.Array where nonce ends / encryptedData begins
-            int tagOffset; // position in ciphertext.Array where encrypted data ends
-
-            checked
+            // arrays are pooled. and they MAY contain non-zeros in the pre-buffer and post-buffer regions.
+            // we could clean them up, but it's not strictly necessary - the important part is that output array
+            // has those pre/post buffer regions, which will be used by the caller.
+            refPooledBuffer.Advance((int)preBufferSize);
+            Encrypt(plaintext, additionalAuthenticatedData, ref refPooledBuffer);
+            refPooledBuffer.Advance((int)postBufferSize);
+
+            CryptoUtil.Assert(refPooledBuffer.WrittenSpan.Length == outputSize, "writtenSpan length should equal calculated outputSize");
+            return refPooledBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
             {
-                keyModifierOffset = plaintext.Offset + (int)preBufferSize;
-                nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
-                encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES;
-                tagOffset = encryptedDataOffset + plaintext.Count;
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
             }
+        }
+    }
 
-            // Randomly generate the key modifier and nonce
+    public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct
+    {
+        try
+        {
+            // Calculate total required size: keyModifier + nonce + plaintext + tag
+            // In GCM, ciphertext length equals plaintext length
+            var totalRequiredSize = checked(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Length + TAG_SIZE_IN_BYTES);
+
+            // Get buffer from writer with the required total size
+            var buffer = destination.GetSpan(totalRequiredSize);
+
+            // Generate random key modifier and nonce
             var keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES);
             var nonceBytes = _genRandom.GenRandom(NONCE_SIZE_IN_BYTES);
 
-            Buffer.BlockCopy(keyModifier, 0, retVal, (int)preBufferSize, keyModifier.Length);
-            Buffer.BlockCopy(nonceBytes, 0, retVal, (int)preBufferSize + keyModifier.Length, nonceBytes.Length);
+            // Copy keyModifier and nonce to buffer
+            keyModifier.CopyTo(buffer.Slice(0, KEY_MODIFIER_SIZE_IN_BYTES));
+            nonceBytes.CopyTo(buffer.Slice(KEY_MODIFIER_SIZE_IN_BYTES, NONCE_SIZE_IN_BYTES));
 
-            // At this point, retVal := { preBuffer | keyModifier | nonce | _____ | _____ | postBuffer }
+            // At this point, buffer := { keyModifier | nonce | _____ | _____ }
 
             // Use the KDF to generate a new symmetric block cipher key
             // We'll need a temporary buffer to hold the symmetric encryption subkey
-            var decryptedKdk = new byte[_keyDerivationKey.Length];
-            var derivedKey = new byte[_derivedkeySizeInBytes];
-            fixed (byte* __unused__1 = decryptedKdk)
+            Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
+                ? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
+                : new byte[_keyDerivationKey.Length];
+
+            Span<byte> derivedKey = _derivedkeySizeInBytes <= 256
+                ? stackalloc byte[256].Slice(0, _derivedkeySizeInBytes)
+                : new byte[_derivedkeySizeInBytes];
+
+            fixed (byte* decryptedKdkUnsafe = decryptedKdk)
             fixed (byte* __unused__2 = derivedKey)
             {
                 try
                 {
-                    _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
+                    _keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
                     ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
                         kdk: decryptedKdk,
                         label: additionalAuthenticatedData,
                         contextHeader: _contextHeader,
                         contextData: keyModifier,
                         operationSubkey: derivedKey,
-                        validationSubkey: Span<byte>.Empty /* filling in derivedKey only */ );
+                        validationSubkey: Span<byte>.Empty /* filling in derivedKey only */);
+
+                    // Perform GCM encryption. Buffer expected structure:
+                    // { keyModifier | nonce | encryptedData | authenticationTag }
+                    var nonce = buffer.Slice(KEY_MODIFIER_SIZE_IN_BYTES, NONCE_SIZE_IN_BYTES);
+                    var encrypted = buffer.Slice(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES, plaintext.Length);
+                    var tag = buffer.Slice(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + plaintext.Length, TAG_SIZE_IN_BYTES);
 
-                    // do gcm
-                    var nonce = new Span<byte>(retVal, nonceOffset, NONCE_SIZE_IN_BYTES);
-                    var tag = new Span<byte>(retVal, tagOffset, TAG_SIZE_IN_BYTES);
-                    var encrypted = new Span<byte>(retVal, encryptedDataOffset, plaintext.Count);
                     using var aes = new AesGcm(derivedKey, TAG_SIZE_IN_BYTES);
                     aes.Encrypt(nonce, plaintext, encrypted, tag);
 
-                    // At this point, retVal := { preBuffer | keyModifier | nonce | encryptedData | authenticationTag | postBuffer }
+                    // At this point, buffer := { keyModifier | nonce | encryptedData | authenticationTag }
                     // And we're done!
-                    return retVal;
+                    destination.Advance(totalRequiredSize);
                 }
                 finally
                 {
                     // delete since these contain secret material
-                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
-                    Array.Clear(derivedKey, 0, derivedKey.Length);
+                    decryptedKdk.Clear();
+                    derivedKey.Clear();
                 }
             }
         }
@@ -216,10 +286,6 @@ internal sealed unsafe class AesGcmAuthenticatedEncryptor : IOptimizedAuthentica
             throw Error.CryptCommon_GenericError(ex);
         }
     }
-
-    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
-        => Encrypt(plaintext, additionalAuthenticatedData, 0, 0);
-
     public void Dispose()
     {
         _keyDerivationKey.Dispose();

+ 453 - 303
src/DataProtection/DataProtection/src/Managed/ManagedAuthenticatedEncryptor.cs

@@ -18,6 +18,9 @@ namespace Microsoft.AspNetCore.DataProtection.Managed;
 // The payloads produced by this encryptor should be compatible with the payloads
 // produced by the CNG-based Encrypt(CBC) + HMAC authenticated encryptor.
 internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncryptor, IDisposable
+#if NET
+    , ISpanAuthenticatedEncryptor
+#endif
 {
     // Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single
     // key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block
@@ -69,155 +72,47 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
         _contextHeader = CreateContextHeader();
     }
 
-    private byte[] CreateContextHeader()
+#if NET
+    public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct
     {
-        var EMPTY_ARRAY = Array.Empty<byte>();
-        var EMPTY_ARRAY_SEGMENT = new ArraySegment<byte>(EMPTY_ARRAY);
-
-        var retVal = new byte[checked(
-            1 /* KDF alg */
-            + 1 /* chaining mode */
-            + sizeof(uint) /* sym alg key size */
-            + sizeof(uint) /* sym alg block size */
-            + sizeof(uint) /* hmac alg key size */
-            + sizeof(uint) /* hmac alg digest size */
-            + _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
-            + _validationAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
-
-        var idx = 0;
-
-        // First is the two-byte header
-        retVal[idx++] = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
-        retVal[idx++] = 0; // 0x00 = CBC encryption + HMAC authentication
-
-        // Next is information about the symmetric algorithm (key size followed by block size)
-        BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmSubkeyLengthInBytes);
-        BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmBlockSizeInBytes);
-
-        // Next is information about the keyed hash algorithm (key size followed by digest size)
-        BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmSubkeyLengthInBytes);
-        BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmDigestLengthInBytes);
-
-        // See the design document for an explanation of the following code.
-        var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _validationAlgorithmSubkeyLengthInBytes];
-        ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
-            kdk: EMPTY_ARRAY,
-            label: EMPTY_ARRAY_SEGMENT,
-            contextHeader: EMPTY_ARRAY_SEGMENT,
-            contextData: EMPTY_ARRAY_SEGMENT,
-            operationSubkey: tempKeys.AsSpan(0, _symmetricAlgorithmSubkeyLengthInBytes),
-            validationSubkey: tempKeys.AsSpan(_symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes));
-
-        // At this point, tempKeys := { K_E || K_H }.
-
-        // Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
-        using (var symmetricAlg = CreateSymmetricAlgorithm())
+        try
         {
-            using (var cryptoTransform = symmetricAlg.CreateEncryptor(
-                rgbKey: new ArraySegment<byte>(tempKeys, 0, _symmetricAlgorithmSubkeyLengthInBytes).AsStandaloneArray(),
-                rgbIV: new byte[_symmetricAlgorithmBlockSizeInBytes]))
+            // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
+            if (ciphertext.Length < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes))
             {
-                var ciphertext = cryptoTransform.TransformFinalBlock(EMPTY_ARRAY, 0, 0);
-                CryptoUtil.Assert(ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes, "ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes");
-                Buffer.BlockCopy(ciphertext, 0, retVal, idx, ciphertext.Length);
+                throw Error.CryptCommon_PayloadInvalid();
             }
-        }
-
-        idx += _symmetricAlgorithmBlockSizeInBytes;
-
-        // MAC a zero-length input string and copy the digest to the return buffer.
-        using (var hashAlg = CreateValidationAlgorithm(new ArraySegment<byte>(tempKeys, _symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes).AsStandaloneArray()))
-        {
-            var digest = hashAlg.ComputeHash(EMPTY_ARRAY);
-            CryptoUtil.Assert(digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes, "digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes");
-            Buffer.BlockCopy(digest, 0, retVal, idx, digest.Length);
-        }
-
-        idx += _validationAlgorithmDigestLengthInBytes;
-        CryptoUtil.Assert(idx == retVal.Length, "idx == retVal.Length");
 
-        // retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || macAlgKeySize || macAlgDigestSize || E("") || MAC("") }.
-        return retVal;
-    }
-
-    private SymmetricAlgorithm CreateSymmetricAlgorithm()
-    {
-        var retVal = _symmetricAlgorithmFactory();
-        CryptoUtil.Assert(retVal != null, "retVal != null");
-
-        retVal.Mode = CipherMode.CBC;
-        retVal.Padding = PaddingMode.PKCS7;
-
-        return retVal;
-    }
-
-    private KeyedHashAlgorithm CreateValidationAlgorithm(byte[]? key = null)
-    {
-        var retVal = _validationAlgorithmFactory();
-        CryptoUtil.Assert(retVal != null, "retVal != null");
-
-        if (key is not null)
-        {
-            retVal.Key = key;
-        }
-        return retVal;
-    }
-
-    public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> additionalAuthenticatedData)
-    {
-        // Assumption: protectedPayload := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
-        protectedPayload.Validate();
-        additionalAuthenticatedData.Validate();
+            // Calculate the maximum possible plaintext size
+            var estimatedDecryptedSize = ciphertext.Length - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes);
 
-        // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
-        if (protectedPayload.Count < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes))
-        {
-            throw Error.CryptCommon_PayloadInvalid();
-        }
-
-        try
-        {
-            // Step 1: Extract the key modifier and IV from the payload.
-            int keyModifierOffset; // position in protectedPayload.Array where key modifier begins
-            int ivOffset; // position in protectedPayload.Array where key modifier ends / IV begins
-            int ciphertextOffset; // position in protectedPayload.Array where IV ends / ciphertext begins
-            int macOffset; // position in protectedPayload.Array where ciphertext ends / MAC begins
-            int eofOffset; // position in protectedPayload.Array where MAC ends
+            // Get buffer from writer with the estimated plaintext size
+            var buffer = destination.GetSpan(estimatedDecryptedSize);
 
-            checked
-            {
-                keyModifierOffset = protectedPayload.Offset;
-                ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
-                ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes;
-            }
+            // Calculate offsets in the ciphertext
+            var keyModifierOffset = 0;
+            var ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
+            var ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes;
+            var macOffset = ciphertext.Length - _validationAlgorithmDigestLengthInBytes;
 
-            ReadOnlySpan<byte> keyModifier = protectedPayload.Array!.AsSpan(keyModifierOffset, ivOffset - keyModifierOffset);
+            // Extract spans for each component
+            var keyModifier = ciphertext.Slice(keyModifierOffset, KEY_MODIFIER_SIZE_IN_BYTES);
 
-            // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
-#if NET10_0_OR_GREATER
+            // Decrypt the KDK and use it to restore the original encryption and MAC keys
             Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
                 ? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
                 : new byte[_keyDerivationKey.Length];
-#else
-            var decryptedKdk = new byte[_keyDerivationKey.Length];
-#endif
 
             byte[]? validationSubkeyArray = null;
             var validationSubkey = _validationAlgorithmSubkeyLengthInBytes <= 128
                 ? stackalloc byte[128].Slice(0, _validationAlgorithmSubkeyLengthInBytes)
                 : (validationSubkeyArray = new byte[_validationAlgorithmSubkeyLengthInBytes]);
 
-#if NET10_0_OR_GREATER
-            Span<byte> decryptionSubkey =
-                _symmetricAlgorithmSubkeyLengthInBytes <= 128
+            Span<byte> decryptionSubkey = _symmetricAlgorithmSubkeyLengthInBytes <= 128
                 ? stackalloc byte[128].Slice(0, _symmetricAlgorithmSubkeyLengthInBytes)
-                : new byte[_symmetricAlgorithmBlockSizeInBytes];
-#else
-            byte[] decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
-#endif
+                : new byte[_symmetricAlgorithmSubkeyLengthInBytes];
 
-            // calling "fixed" is basically pinning the array, meaning the GC won't move it around. (Also for safety concerns)
-            // note: it is safe to call `fixed` on null - it is just a no-op
             fixed (byte* decryptedKdkUnsafe = decryptedKdk)
             fixed (byte* __unused__2 = decryptionSubkey)
             fixed (byte* __unused__3 = validationSubkeyArray)
@@ -233,57 +128,34 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
                         operationSubkey: decryptionSubkey,
                         validationSubkey: validationSubkey);
 
-                    // Step 3: Calculate the correct MAC for this payload.
-                    // correctHash := MAC(IV || ciphertext)
-                    checked
+                    // Validate the MAC provided as part of the payload
+                    var ivAndCiphertextSpan = ciphertext.Slice(ivOffset, macOffset - ivOffset);
+                    var providedMac = ciphertext.Slice(macOffset, _validationAlgorithmDigestLengthInBytes);
+
+                    if (!ValidateMac(ivAndCiphertextSpan, providedMac, validationSubkey, validationSubkeyArray))
                     {
-                        eofOffset = protectedPayload.Offset + protectedPayload.Count;
-                        macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes;
+                        throw Error.CryptCommon_PayloadInvalid();
                     }
 
-                    // Step 4: Validate the MAC provided as part of the payload.
-                    CalculateAndValidateMac(protectedPayload.Array!, ivOffset, macOffset, eofOffset, validationSubkey, validationSubkeyArray);
+                    // If the integrity check succeeded, decrypt the payload directly into destination
+                    var ciphertextSpan = ciphertext.Slice(ciphertextOffset, macOffset - ciphertextOffset);
+                    var iv = ciphertext.Slice(ivOffset, _symmetricAlgorithmBlockSizeInBytes);
 
-                    // Step 5: Decipher the ciphertext and return it to the caller.
-#if NET10_0_OR_GREATER
                     using var symmetricAlgorithm = CreateSymmetricAlgorithm();
-                    symmetricAlgorithm.SetKey(decryptionSubkey); // setKey is a single-shot usage of symmetricAlgorithm. Not allocatey
+                    symmetricAlgorithm.SetKey(decryptionSubkey);
 
-                    // note: here protectedPayload.Array is taken without an offset (can't use AsSpan() on ArraySegment)
-                    var ciphertext = protectedPayload.Array.AsSpan(ciphertextOffset, macOffset - ciphertextOffset);
-                    var iv = protectedPayload.Array.AsSpan(ivOffset, _symmetricAlgorithmBlockSizeInBytes);
-
-                    // symmetricAlgorithm is created with CBC mode (see CreateSymmetricAlgorithm())
-                    return symmetricAlgorithm.DecryptCbc(ciphertext, iv);
-#else
-                    var iv = protectedPayload.Array!.AsSpan(ivOffset, _symmetricAlgorithmBlockSizeInBytes).ToArray();
-
-                    using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
-                    using (var cryptoTransform = symmetricAlgorithm.CreateDecryptor(decryptionSubkey, iv))
-                    {
-                        var outputStream = new MemoryStream();
-                        using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
-                        {
-                            cryptoStream.Write(protectedPayload.Array!, ciphertextOffset, macOffset - ciphertextOffset);
-                            cryptoStream.FlushFinalBlock();
+                    // Decrypt directly into destination buffer
+                    var actualDecryptedBytes = symmetricAlgorithm.DecryptCbc(ciphertextSpan, iv, buffer);
 
-                            // At this point, outputStream := { plaintext }, and we're done!
-                            return outputStream.ToArray();
-                        }
-                    }
-#endif
+                    // Advance the writer by the actual number of bytes written
+                    destination.Advance(actualDecryptedBytes);
                 }
                 finally
                 {
                     // delete since these contain secret material
                     validationSubkey.Clear();
-
-#if NET10_0_OR_GREATER
                     decryptedKdk.Clear();
                     decryptionSubkey.Clear();
-#else
-                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
-#endif
                 }
             }
         }
@@ -294,62 +166,57 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
         }
     }
 
-    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
+    public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : IBufferWriter<byte>, allows ref struct
     {
-        plaintext.Validate();
-        additionalAuthenticatedData.Validate();
-        var plainTextSpan = plaintext.AsSpan();
-
         try
         {
             var keyModifierLength = KEY_MODIFIER_SIZE_IN_BYTES;
             var ivLength = _symmetricAlgorithmBlockSizeInBytes;
 
-#if NET10_0_OR_GREATER
+            // Calculate the required total size upfront
+            using var symmetricAlgorithmForSizing = CreateSymmetricAlgorithm();
+            var cipherTextLength = symmetricAlgorithmForSizing.GetCiphertextLengthCbc(plaintext.Length);
+            var macLength = _validationAlgorithmDigestLengthInBytes;
+            var totalRequiredSize = keyModifierLength + ivLength + cipherTextLength + macLength;
+
+            // Get buffer from writer with the required total size
+            var buffer = destination.GetSpan(totalRequiredSize);
+
+            // Generate random key modifier and IV
+            Span<byte> keyModifier = keyModifierLength <= 128
+                ? stackalloc byte[128].Slice(0, keyModifierLength)
+                : new byte[keyModifierLength];
+
+            _genRandom.GenRandom(keyModifier);
+
+            // Copy key modifier to buffer
+            keyModifier.CopyTo(buffer.Slice(0, keyModifierLength));
+
+            // Generate IV directly into buffer
+            var iv = buffer.Slice(keyModifierLength, ivLength);
+            _genRandom.GenRandom(iv);
+
+            // Use the KDF to generate a new symmetric block cipher key
             Span<byte> decryptedKdk = _keyDerivationKey.Length <= 256
                 ? stackalloc byte[256].Slice(0, _keyDerivationKey.Length)
                 : new byte[_keyDerivationKey.Length];
-#else
-            var decryptedKdk = new byte[_keyDerivationKey.Length];
-#endif
 
-#if NET10_0_OR_GREATER
             byte[]? validationSubkeyArray = null;
             Span<byte> validationSubkey = _validationAlgorithmSubkeyLengthInBytes <= 128
                 ? stackalloc byte[128].Slice(0, _validationAlgorithmSubkeyLengthInBytes)
                 : (validationSubkeyArray = new byte[_validationAlgorithmSubkeyLengthInBytes]);
-#else
-            var validationSubkeyArray = new byte[_validationAlgorithmSubkeyLengthInBytes];
-            var validationSubkey = validationSubkeyArray.AsSpan();
-#endif
 
-#if NET10_0_OR_GREATER
             Span<byte> encryptionSubkey = _symmetricAlgorithmSubkeyLengthInBytes <= 128
                 ? stackalloc byte[128].Slice(0, _symmetricAlgorithmSubkeyLengthInBytes)
                 : new byte[_symmetricAlgorithmSubkeyLengthInBytes];
-#else
-            byte[] encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
-#endif
 
             fixed (byte* decryptedKdkUnsafe = decryptedKdk)
             fixed (byte* __unused__1 = encryptionSubkey)
             fixed (byte* __unused__2 = validationSubkeyArray)
             {
-                // Step 1: Generate a random key modifier and IV for this operation.
-                // Both will be equal to the block size of the block cipher algorithm.
-#if NET10_0_OR_GREATER
-                Span<byte> keyModifier = keyModifierLength <= 128
-                    ? stackalloc byte[128].Slice(0, keyModifierLength)
-                    : new byte[keyModifierLength];
-
-                _genRandom.GenRandom(keyModifier);
-#else
-                var keyModifier = _genRandom.GenRandom(keyModifierLength);
-#endif
-
                 try
                 {
-                    // Step 2: Decrypt the KDK, and use it to generate new encryption and HMAC keys.
                     _keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
                     ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
                         kdk: decryptedKdk,
@@ -359,61 +226,20 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
                         operationSubkey: encryptionSubkey,
                         validationSubkey: validationSubkey);
 
-#if NET10_0_OR_GREATER
-                    // idea of optimization here is firstly get all the types preset
-                    // for calculating length of the output array and allocating it.
-                    // then we are filling it with the data directly, without any additional copying
-
                     using var symmetricAlgorithm = CreateSymmetricAlgorithm();
-                    symmetricAlgorithm.SetKey(encryptionSubkey); // setKey is a single-shot usage of symmetricAlgorithm. Not allocatey
+                    symmetricAlgorithm.SetKey(encryptionSubkey);
 
                     using var validationAlgorithm = CreateValidationAlgorithm();
 
-                    // Later framework has an API to pre-calculate optimal length of the ciphertext.
-                    // That means we can avoid allocating more data than we need.
-
-                    var cipherTextLength = symmetricAlgorithm.GetCiphertextLengthCbc(plainTextSpan.Length); // CBC because symmetricAlgorithm is created with CBC mode
-                    var macLength = _validationAlgorithmDigestLengthInBytes;
-
-                    // allocating an array of a specific required length
-                    var outputArray = new byte[keyModifierLength + ivLength + cipherTextLength + macLength];
-                    var outputSpan = outputArray.AsSpan();
-#else
-                    var outputStream = new MemoryStream();
-#endif
-
-#if NET10_0_OR_GREATER
-                    // Step 2: Copy the key modifier to the output stream (part of a header)
-                    keyModifier.CopyTo(outputSpan.Slice(start: 0, length: keyModifierLength));
-
-                    // Step 3: Generate IV for this operation right into the output stream (no allocation)
-                    // key modifier and IV together act as a header.
-                    var iv = outputSpan.Slice(start: keyModifierLength, length: ivLength);
-                    _genRandom.GenRandom(iv);
-#else
-                    // Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header.
-                    outputStream.Write(keyModifier, 0, keyModifier.Length);
-
-                    // Step 3: Generate IV for this operation right into the result array
-                    var iv = _genRandom.GenRandom(_symmetricAlgorithmBlockSizeInBytes);
-                    outputStream.Write(iv, 0, iv.Length);
-#endif
-
-#if NET10_0_OR_GREATER
-                    // Step 4: Perform the encryption operation.
-                    // encrypting plaintext into the target array directly
-                    symmetricAlgorithm.EncryptCbc(plainTextSpan, iv, outputSpan.Slice(start: keyModifierLength + ivLength, length: cipherTextLength));
-
-                    // At this point, outputStream := { keyModifier || IV || ciphertext }
-
-                    // Step 5: Calculate the digest over the IV and ciphertext.
-                    // We don't need to calculate the digest over the key modifier since that
-                    // value has already been mixed into the KDF used to generate the MAC key.
+                    // Perform the encryption operation
+                    var ciphertextDestination = buffer.Slice(keyModifierLength + ivLength, cipherTextLength);
+                    symmetricAlgorithm.EncryptCbc(plaintext, iv, ciphertextDestination);
 
-                    var ivAndCipherTextSpan = outputSpan.Slice(start: keyModifierLength, length: ivLength + cipherTextLength);
-                    var macDestinationSpan = outputSpan.Slice(keyModifierLength + ivLength + cipherTextLength, macLength);
+                    // Calculate the digest over the IV and ciphertext
+                    var ivAndCipherTextSpan = buffer.Slice(keyModifierLength, ivLength + cipherTextLength);
+                    var macDestinationSpan = buffer.Slice(keyModifierLength + ivLength + cipherTextLength, macLength);
 
-                    // if we can use an optimized method for specific algorithm - we use it (no extra alloc for subKey)
+                    // Use optimized method for specific algorithms when possible
                     if (validationAlgorithm is HMACSHA256)
                     {
                         HMACSHA256.HashData(key: validationSubkey, source: ivAndCipherTextSpan, destination: macDestinationSpan);
@@ -425,49 +251,21 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
                     else
                     {
                         validationAlgorithm.Key = validationSubkeyArray ?? validationSubkey.ToArray();
-                        validationAlgorithm.TryComputeHash(source: ivAndCipherTextSpan, destination: macDestinationSpan, bytesWritten: out _);
-                    }
-
-                    // At this point, outputArray := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
-                    return outputArray;
-#else
-                    // Step 4: Perform the encryption operation.
-                    using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
-                    using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
-                    using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
-                    {
-                        cryptoStream.Write(plaintext.Array!, plaintext.Offset, plaintext.Count);
-                        cryptoStream.FlushFinalBlock();
-
-                        // At this point, outputStream := { keyModifier || IV || ciphertext }
-
-                        // Step 5: Calculate the digest over the IV and ciphertext.
-                        // We don't need to calculate the digest over the key modifier since that
-                        // value has already been mixed into the KDF used to generate the MAC key.
-                        using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkeyArray))
+                        if (!validationAlgorithm.TryComputeHash(source: ivAndCipherTextSpan, destination: macDestinationSpan, out _))
                         {
-                            // As an optimization, avoid duplicating the underlying buffer
-                            var underlyingBuffer = outputStream.GetBuffer();
-
-                            var mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES));
-                            outputStream.Write(mac, 0, mac.Length);
-
-                            // At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
-                            // And we're done!
-                            return outputStream.ToArray();
+                            throw Error.CryptCommon_GenericError(new InvalidOperationException("Failed to compute HMAC"));
                         }
                     }
-#endif
+
+                    // Advance the writer by the total bytes written
+                    destination.Advance(totalRequiredSize);
                 }
                 finally
                 {
-#if NET10_0_OR_GREATER
                     keyModifier.Clear();
                     decryptedKdk.Clear();
-#else
-                    Array.Clear(keyModifier, 0, keyModifierLength);
-                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
-#endif
+                    validationSubkey.Clear();
+                    encryptionSubkey.Clear();
                 }
             }
         }
@@ -477,49 +275,179 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
             throw Error.CryptCommon_GenericError(ex);
         }
     }
+#endif
 
-    private void CalculateAndValidateMac(
-        byte[] payloadArray,
-        int ivOffset, int macOffset, int eofOffset, // offsets to slice the payload array
-        ReadOnlySpan<byte> validationSubkey,
-        byte[]? validationSubkeyArray)
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData)
     {
-        using var validationAlgorithm = CreateValidationAlgorithm();
-        var hashSize = validationAlgorithm.GetDigestSizeInBytes();
+        plaintext.Validate();
+        additionalAuthenticatedData.Validate();
+        var plainTextSpan = plaintext.AsSpan();
 
-        byte[]? correctHashArray = null;
-        Span<byte> correctHash = hashSize <= 128
-            ? stackalloc byte[128].Slice(0, hashSize)
-            : (correctHashArray = new byte[hashSize]);
+#if NET
+        var symmetricAlgorithm = CreateSymmetricAlgorithm();
+        var cipherTextLength = symmetricAlgorithm.GetCiphertextLengthCbc(plaintext.Count);
+        var outputSize = KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes /* IV */ + cipherTextLength + _validationAlgorithmDigestLengthInBytes /* MAC */;
+
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
 
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
         try
         {
-#if NET10_0_OR_GREATER
-            var hashSource = payloadArray!.AsSpan(ivOffset, macOffset - ivOffset);
-
-            int bytesWritten;
-            if (validationAlgorithm is HMACSHA256)
-            {
-                bytesWritten = HMACSHA256.HashData(key: validationSubkey, source: hashSource, destination: correctHash);
+            Encrypt(plaintext, additionalAuthenticatedData, ref refPooledBuffer);
+            CryptoUtil.Assert(refPooledBuffer.WrittenSpan.Length == outputSize, "bytesWritten == size");
+            return refPooledBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+#else
+        try
+        {
+            var keyModifierLength = KEY_MODIFIER_SIZE_IN_BYTES;
+            var ivLength = _symmetricAlgorithmBlockSizeInBytes;
+
+            var decryptedKdk = new byte[_keyDerivationKey.Length];
+            var validationSubkeyArray = new byte[_validationAlgorithmSubkeyLengthInBytes];
+            var validationSubkey = validationSubkeyArray.AsSpan();
+            byte[] encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
+
+            fixed (byte* decryptedKdkUnsafe = decryptedKdk)
+            fixed (byte* __unused__1 = encryptionSubkey)
+            fixed (byte* __unused__2 = validationSubkeyArray)
+            {
+                // Step 1: Generate a random key modifier and IV for this operation.
+                // Both will be equal to the block size of the block cipher algorithm.
+                var keyModifier = _genRandom.GenRandom(keyModifierLength);
+
+                try
+                {
+                    // Step 2: Decrypt the KDK, and use it to generate new encryption and HMAC keys.
+                    _keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
+                    ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
+                        kdk: decryptedKdk,
+                        label: additionalAuthenticatedData,
+                        contextHeader: _contextHeader,
+                        contextData: keyModifier,
+                        operationSubkey: encryptionSubkey,
+                        validationSubkey: validationSubkey);
+
+                    var outputStream = new MemoryStream();
+
+                    // Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header.
+                    outputStream.Write(keyModifier, 0, keyModifier.Length);
+
+                    // Step 3: Generate IV for this operation right into the result array
+                    var iv = _genRandom.GenRandom(_symmetricAlgorithmBlockSizeInBytes);
+                    outputStream.Write(iv, 0, iv.Length);
+
+                    // Step 4: Perform the encryption operation.
+                    using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
+                    using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
+                    using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
+                    {
+                        cryptoStream.Write(plaintext.Array!, plaintext.Offset, plaintext.Count);
+                        cryptoStream.FlushFinalBlock();
+
+                        // At this point, outputStream := { keyModifier || IV || ciphertext }
+
+                        // Step 5: Calculate the digest over the IV and ciphertext.
+                        // We don't need to calculate the digest over the key modifier since that
+                        // value has already been mixed into the KDF used to generate the MAC key.
+                        using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkeyArray))
+                        {
+                            // As an optimization, avoid duplicating the underlying buffer
+                            var underlyingBuffer = outputStream.GetBuffer();
+
+                            var mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES));
+                            outputStream.Write(mac, 0, mac.Length);
+
+                            // At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
+                            // And we're done!
+                            return outputStream.ToArray();
+                        }
+                    }
+                }
+                finally
+                {
+                    Array.Clear(keyModifier, 0, keyModifierLength);
+                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
+                }
+            }
+        }
+        catch (Exception ex) when (ex.RequiresHomogenization())
+        {
+            // Homogenize all exceptions to CryptographicException.
+            throw Error.CryptCommon_GenericError(ex);
+        }
+#endif
+    }
+
+#if NET
+    private bool ValidateMac(ReadOnlySpan<byte> dataToValidate, ReadOnlySpan<byte> providedMac, ReadOnlySpan<byte> validationSubkey, byte[]? validationSubkeyArray)
+    {
+        using var validationAlgorithm = CreateValidationAlgorithm();
+        var hashSize = validationAlgorithm.GetDigestSizeInBytes();
+
+        byte[]? correctHashArray = null;
+        Span<byte> correctHash = hashSize <= 128
+            ? stackalloc byte[128].Slice(0, hashSize)
+            : (correctHashArray = new byte[hashSize]);
+
+        try
+        {
+            int bytesWritten;
+            if (validationAlgorithm is HMACSHA256)
+            {
+                bytesWritten = HMACSHA256.HashData(key: validationSubkey, source: dataToValidate, destination: correctHash);
             }
             else if (validationAlgorithm is HMACSHA512)
             {
-                bytesWritten = HMACSHA512.HashData(key: validationSubkey, source: hashSource, destination: correctHash);
+                bytesWritten = HMACSHA512.HashData(key: validationSubkey, source: dataToValidate, destination: correctHash);
             }
             else
             {
                 // if validationSubkey is stackalloc'ed, there is no way we avoid an alloc here
                 validationAlgorithm.Key = validationSubkeyArray ?? validationSubkey.ToArray();
-                var success = validationAlgorithm.TryComputeHash(hashSource, correctHash, out bytesWritten);
+                var success = validationAlgorithm.TryComputeHash(dataToValidate, correctHash, out bytesWritten);
                 Debug.Assert(success);
             }
-
             Debug.Assert(bytesWritten == hashSize);
+
+            return CryptoUtil.TimeConstantBuffersAreEqual(correctHash, providedMac);
+        }
+        finally
+        {
+            correctHash.Clear();
+        }
+    }
 #else
+    private void CalculateAndValidateMac(
+    byte[] payloadArray,
+    int ivOffset, int macOffset, int eofOffset, // offsets to slice the payload array
+    ReadOnlySpan<byte> validationSubkey,
+    byte[]? validationSubkeyArray)
+    {
+        using var validationAlgorithm = CreateValidationAlgorithm();
+        var hashSize = validationAlgorithm.GetDigestSizeInBytes();
+
+        byte[]? correctHashArray = null;
+        Span<byte> correctHash = hashSize <= 128
+            ? stackalloc byte[128].Slice(0, hashSize)
+            : (correctHashArray = new byte[hashSize]);
+
+        try
+        {
             // if validationSubkey is stackalloc'ed, there is no way we avoid an alloc here
             validationAlgorithm.Key = validationSubkeyArray ?? validationSubkey.ToArray();
             correctHashArray = validationAlgorithm.ComputeHash(payloadArray, macOffset, eofOffset - macOffset);
-#endif
 
             // Step 4: Validate the MAC provided as part of the payload.
             var payloadMacSpan = payloadArray!.AsSpan(macOffset, eofOffset - macOffset);
@@ -533,6 +461,228 @@ internal sealed unsafe class ManagedAuthenticatedEncryptor : IAuthenticatedEncry
             correctHash.Clear();
         }
     }
+#endif
+
+    public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> additionalAuthenticatedData)
+    {
+        // Assumption: protectedPayload := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
+        protectedPayload.Validate();
+        additionalAuthenticatedData.Validate();
+
+#if NET
+        var outputSize = protectedPayload.Count - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes);
+        if (outputSize <= 0)
+        {
+            throw Error.CryptCommon_PayloadInvalid();
+        }
+
+        byte[] rentedBuffer = null!;
+        var buffer = outputSize < 256
+            ? stackalloc byte[255]
+            : (rentedBuffer = ArrayPool<byte>.Shared.Rent(outputSize));
+
+        var refPooledBuffer = new RefPooledArrayBufferWriter<byte>(buffer);
+        try
+        {
+            Decrypt(protectedPayload, additionalAuthenticatedData, ref refPooledBuffer);
+            return refPooledBuffer.WrittenSpan.ToArray();
+        }
+        finally
+        {
+            refPooledBuffer.Dispose();
+            if (rentedBuffer is not null)
+            {
+                ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
+            }
+        }
+#else
+        // Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
+        if (protectedPayload.Count < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes))
+        {
+            throw Error.CryptCommon_PayloadInvalid();
+        }
+
+        try
+        {
+            // Step 1: Extract the key modifier and IV from the payload.
+            int keyModifierOffset; // position in protectedPayload.Array where key modifier begins
+            int ivOffset; // position in protectedPayload.Array where key modifier ends / IV begins
+            int ciphertextOffset; // position in protectedPayload.Array where IV ends / ciphertext begins
+            int macOffset; // position in protectedPayload.Array where ciphertext ends / MAC begins
+            int eofOffset; // position in protectedPayload.Array where MAC ends
+
+            checked
+            {
+                keyModifierOffset = protectedPayload.Offset;
+                ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES;
+                ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes;
+            }
+
+            ReadOnlySpan<byte> keyModifier = protectedPayload.Array!.AsSpan(keyModifierOffset, ivOffset - keyModifierOffset);
+
+            // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
+            var decryptedKdk = new byte[_keyDerivationKey.Length];
+
+            byte[]? validationSubkeyArray = null;
+            var validationSubkey = _validationAlgorithmSubkeyLengthInBytes <= 128
+                ? stackalloc byte[128].Slice(0, _validationAlgorithmSubkeyLengthInBytes)
+                : (validationSubkeyArray = new byte[_validationAlgorithmSubkeyLengthInBytes]);
+
+            byte[] decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
+            // calling "fixed" is basically pinning the array, meaning the GC won't move it around. (Also for safety concerns)
+            // note: it is safe to call `fixed` on null - it is just a no-op
+            fixed (byte* decryptedKdkUnsafe = decryptedKdk)
+            fixed (byte* __unused__2 = decryptionSubkey)
+            fixed (byte* __unused__3 = validationSubkeyArray)
+            {
+                try
+                {
+                    _keyDerivationKey.WriteSecretIntoBuffer(decryptedKdkUnsafe, decryptedKdk.Length);
+                    ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
+                        kdk: decryptedKdk,
+                        label: additionalAuthenticatedData,
+                        contextHeader: _contextHeader,
+                        contextData: keyModifier,
+                        operationSubkey: decryptionSubkey,
+                        validationSubkey: validationSubkey);
+
+                    // Step 3: Calculate the correct MAC for this payload.
+                    // correctHash := MAC(IV || ciphertext)
+                    checked
+                    {
+                        eofOffset = protectedPayload.Offset + protectedPayload.Count;
+                        macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes;
+                    }
+
+                    // Step 4: Validate the MAC provided as part of the payload.
+                    CalculateAndValidateMac(protectedPayload.Array!, ivOffset, macOffset, eofOffset, validationSubkey, validationSubkeyArray);
+
+                    // Step 5: Decipher the ciphertext and return it to the caller.
+                    var iv = protectedPayload.Array!.AsSpan(ivOffset, _symmetricAlgorithmBlockSizeInBytes).ToArray();
+
+                    using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
+                    using (var cryptoTransform = symmetricAlgorithm.CreateDecryptor(decryptionSubkey, iv))
+                    {
+                        var outputStream = new MemoryStream();
+                        using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
+                        {
+                            cryptoStream.Write(protectedPayload.Array!, ciphertextOffset, macOffset - ciphertextOffset);
+                            cryptoStream.FlushFinalBlock();
+
+                            // At this point, outputStream := { plaintext }, and we're done!
+                            return outputStream.ToArray();
+                        }
+                    }
+                }
+                finally
+                {
+                    // delete since these contain secret material
+                    validationSubkey.Clear();
+                    Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
+                }
+            }
+        }
+        catch (Exception ex) when (ex.RequiresHomogenization())
+        {
+            // Homogenize all exceptions to CryptographicException.
+            throw Error.CryptCommon_GenericError(ex);
+        }
+#endif
+    }
+
+    private byte[] CreateContextHeader()
+    {
+        var EMPTY_ARRAY = Array.Empty<byte>();
+        var EMPTY_ARRAY_SEGMENT = new ArraySegment<byte>(EMPTY_ARRAY);
+
+        var retVal = new byte[checked(
+            1 /* KDF alg */
+            + 1 /* chaining mode */
+            + sizeof(uint) /* sym alg key size */
+            + sizeof(uint) /* sym alg block size */
+            + sizeof(uint) /* hmac alg key size */
+            + sizeof(uint) /* hmac alg digest size */
+            + _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */
+            + _validationAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)];
+
+        var idx = 0;
+
+        // First is the two-byte header
+        retVal[idx++] = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF
+        retVal[idx++] = 0; // 0x00 = CBC encryption + HMAC authentication
+
+        // Next is information about the symmetric algorithm (key size followed by block size)
+        BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmSubkeyLengthInBytes);
+        BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmBlockSizeInBytes);
+
+        // Next is information about the keyed hash algorithm (key size followed by digest size)
+        BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmSubkeyLengthInBytes);
+        BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmDigestLengthInBytes);
+
+        // See the design document for an explanation of the following code.
+        var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _validationAlgorithmSubkeyLengthInBytes];
+        ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
+            kdk: EMPTY_ARRAY,
+            label: EMPTY_ARRAY_SEGMENT,
+            contextHeader: EMPTY_ARRAY_SEGMENT,
+            contextData: EMPTY_ARRAY_SEGMENT,
+            operationSubkey: tempKeys.AsSpan(0, _symmetricAlgorithmSubkeyLengthInBytes),
+            validationSubkey: tempKeys.AsSpan(_symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes));
+
+        // At this point, tempKeys := { K_E || K_H }.
+
+        // Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer.
+        using (var symmetricAlg = CreateSymmetricAlgorithm())
+        {
+            using (var cryptoTransform = symmetricAlg.CreateEncryptor(
+                rgbKey: new ArraySegment<byte>(tempKeys, 0, _symmetricAlgorithmSubkeyLengthInBytes).AsStandaloneArray(),
+                rgbIV: new byte[_symmetricAlgorithmBlockSizeInBytes]))
+            {
+                var ciphertext = cryptoTransform.TransformFinalBlock(EMPTY_ARRAY, 0, 0);
+                CryptoUtil.Assert(ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes, "ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes");
+                Buffer.BlockCopy(ciphertext, 0, retVal, idx, ciphertext.Length);
+            }
+        }
+
+        idx += _symmetricAlgorithmBlockSizeInBytes;
+
+        // MAC a zero-length input string and copy the digest to the return buffer.
+        using (var hashAlg = CreateValidationAlgorithm(new ArraySegment<byte>(tempKeys, _symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes).AsStandaloneArray()))
+        {
+            var digest = hashAlg.ComputeHash(EMPTY_ARRAY);
+            CryptoUtil.Assert(digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes, "digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes");
+            Buffer.BlockCopy(digest, 0, retVal, idx, digest.Length);
+        }
+
+        idx += _validationAlgorithmDigestLengthInBytes;
+        CryptoUtil.Assert(idx == retVal.Length, "idx == retVal.Length");
+
+        // retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || macAlgKeySize || macAlgDigestSize || E("") || MAC("") }.
+        return retVal;
+    }
+
+    private SymmetricAlgorithm CreateSymmetricAlgorithm()
+    {
+        var retVal = _symmetricAlgorithmFactory();
+        CryptoUtil.Assert(retVal != null, "retVal != null");
+
+        retVal.Mode = CipherMode.CBC;
+        retVal.Padding = PaddingMode.PKCS7;
+
+        return retVal;
+    }
+
+    private KeyedHashAlgorithm CreateValidationAlgorithm(byte[]? key = null)
+    {
+        var retVal = _validationAlgorithmFactory();
+        CryptoUtil.Assert(retVal != null, "retVal != null");
+
+        if (key is not null)
+        {
+            retVal.Key = key;
+        }
+        return retVal;
+    }
 
     public void Dispose()
     {

+ 9 - 3
src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <Description>ASP.NET Core logic to protect and unprotect data, similar to DPAPI.</Description>
@@ -16,9 +16,10 @@
 
   <ItemGroup>
     <Compile Include="..\..\shared\src\*.cs" LinkBase="Shared" />
-    <Compile Include="$(SharedSourceRoot)PlatformAttributes.cs" LinkBase="Shared"
-      Condition="'$(TargetFramework)' != '$(DefaultNetCoreTargetFramework)'" />
+    <Compile Include="$(SharedSourceRoot)PlatformAttributes.cs" LinkBase="Shared" Condition="'$(TargetFramework)' != '$(DefaultNetCoreTargetFramework)'" />
     <Compile Include="$(SharedSourceRoot)TrimmingAttributes.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)\Buffers\RefPooledArrayBufferWriter.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)\PooledArrayBufferWriter.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentNullThrowHelper.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)CallerArgument\CallerArgumentExpressionAttribute.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)Encoding\Int7BitEncodingUtils.cs" LinkBase="Shared" />
@@ -54,4 +55,9 @@
     <InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
     <InternalsVisibleTo Include="KeyManagementSimulator" />
   </ItemGroup>
+
+  <ItemGroup>
+    <AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
+    <AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
+  </ItemGroup>
 </Project>

+ 0 - 0
src/DataProtection/DataProtection/src/PublicAPI.Shipped.txt → src/DataProtection/DataProtection/src/PublicAPI/net10.0/PublicAPI.Shipped.txt


+ 4 - 0
src/DataProtection/DataProtection/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt

@@ -0,0 +1,4 @@
+#nullable enable
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ISpanAuthenticatedEncryptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ISpanAuthenticatedEncryptor.Decrypt<TWriter>(System.ReadOnlySpan<byte> ciphertext, System.ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ISpanAuthenticatedEncryptor.Encrypt<TWriter>(System.ReadOnlySpan<byte> plaintext, System.ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) -> void

+ 289 - 0
src/DataProtection/DataProtection/src/PublicAPI/net462/PublicAPI.Shipped.txt

@@ -0,0 +1,289 @@
+#nullable enable
+abstract Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory.AuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory.CngCbcAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CngGcmAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration.AlgorithmConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.AuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.ValidationAlgorithm.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.ValidationAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor.AuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer.AuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.CngCbcAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor.CngCbcAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer.CngCbcAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.CngGcmAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor.CngGcmAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer.CngGcmAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmType.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ManagedAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ValidationAlgorithmType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ValidationAlgorithmType.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor.ManagedAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer.ManagedAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlExtensions
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.DeserializerType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.SerializedDescriptorElement.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.XmlSerializedDescriptorInfo(System.Xml.Linq.XElement! serializedDescriptorElement, System.Type! deserializerType) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_128_CBC = 0 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_128_GCM = 3 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_192_CBC = 1 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_192_GCM = 4 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_CBC = 2 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_GCM = 5 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.Decrypt(System.ArraySegment<byte> ciphertext, System.ArraySegment<byte> additionalAuthenticatedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.Encrypt(System.ArraySegment<byte> plaintext, System.ArraySegment<byte> additionalAuthenticatedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory.ManagedAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA256 = 0 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA512 = 1 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.ApplicationDiscriminator.get -> string?
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.ApplicationDiscriminator.set -> void
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.DataProtectionOptions() -> void
+Microsoft.AspNetCore.DataProtection.DataProtectionUtilityExtensions
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.CreateProtector(string! purpose) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.EphemeralDataProtectionProvider() -> void
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.EphemeralDataProtectionProvider(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder
+Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
+Microsoft.AspNetCore.DataProtection.Internal.IActivator
+Microsoft.AspNetCore.DataProtection.Internal.IActivator.CreateInstance(System.Type! expectedBaseType, string! implementationTypeName) -> object!
+Microsoft.AspNetCore.DataProtection.IPersistedDataProtector
+Microsoft.AspNetCore.DataProtection.IPersistedDataProtector.DangerousUnprotect(byte[]! protectedData, bool ignoreRevocationErrors, out bool requiresMigration, out bool wasRevoked) -> byte[]!
+Microsoft.AspNetCore.DataProtection.ISecret
+Microsoft.AspNetCore.DataProtection.ISecret.Length.get -> int
+Microsoft.AspNetCore.DataProtection.ISecret.WriteSecretIntoBuffer(System.ArraySegment<byte> buffer) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager.CanDeleteKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager.DeleteKeys(System.Func<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!, bool>! shouldDelete) -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.ActivationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.CreateEncryptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.CreationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.Descriptor.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.ExpirationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.IsRevoked.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.KeyId.get -> System.Guid
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink.Store(System.Guid keyId, System.Xml.Linq.XElement! element) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.CreateNewKey(System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.GetAllKeys() -> System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.GetCacheExpirationToken() -> System.Threading.CancellationToken
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.RevokeAllKeys(System.DateTimeOffset revocationDate, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.RevokeKey(System.Guid keyId, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.CacheableKeyRing
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.DefaultKey -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.DefaultKeyResolution() -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.FallbackKey -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.ShouldGenerateNewKey -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.ICacheableKeyRingProvider
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.ICacheableKeyRingProvider.GetCacheableKeyRing(System.DateTimeOffset now) -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.CacheableKeyRing!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IDefaultKeyResolver
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IDefaultKeyResolver.ResolveDefaultKeyPolicy(System.DateTimeOffset now, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>! allKeys) -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.CreateNewKey(System.Guid keyId, System.DateTimeOffset creationDate, System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(System.Xml.Linq.XElement! keyElement) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.RevokeSingleKey(System.Guid keyId, System.DateTimeOffset revocationDate, string? reason) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.DefaultAuthenticatedEncryptor.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.DefaultKeyId.get -> System.Guid
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.GetAuthenticatedEncryptorByKeyId(System.Guid keyId, out bool isRevoked) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRingProvider
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRingProvider.GetCurrentKeyRing() -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorConfiguration.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorConfiguration.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorFactories.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AutoGenerateKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AutoGenerateKeys.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.KeyEscrowSinks.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.KeyManagementOptions() -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.NewKeyLifetime.get -> System.TimeSpan
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.NewKeyLifetime.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlEncryptor.get -> Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlEncryptor.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlRepository.get -> Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlRepository.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.CanDeleteKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.CreateNewKey(System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.DeleteKeys(System.Func<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!, bool>! shouldDelete) -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.GetAllKeys() -> System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.GetCacheExpirationToken() -> System.Threading.CancellationToken
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.RevokeAllKeys(System.DateTimeOffset revocationDate, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.RevokeKey(System.Guid keyId, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.XmlKeyManager(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! keyManagementOptions, Microsoft.AspNetCore.DataProtection.Internal.IActivator! activator, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.XmlKeyManager(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! keyManagementOptions, Microsoft.AspNetCore.DataProtection.Internal.IActivator! activator) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.Directory.get -> System.IO.DirectoryInfo!
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.FileSystemXmlRepository(System.IO.DirectoryInfo! directory, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.DeletionOrder.get -> int?
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.DeletionOrder.set -> void
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.Element.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.RegistryKey.get -> Microsoft.Win32.RegistryKey!
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.RegistryXmlRepository(Microsoft.Win32.RegistryKey! registryKey, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.Secret
+Microsoft.AspNetCore.DataProtection.Secret.Dispose() -> void
+Microsoft.AspNetCore.DataProtection.Secret.Length.get -> int
+Microsoft.AspNetCore.DataProtection.Secret.Secret(byte[]! value) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(byte* secret, int secretLength) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(Microsoft.AspNetCore.DataProtection.ISecret! secret) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(System.ArraySegment<byte> value) -> void
+Microsoft.AspNetCore.DataProtection.Secret.WriteSecretIntoBuffer(byte* buffer, int bufferLength) -> void
+Microsoft.AspNetCore.DataProtection.Secret.WriteSecretIntoBuffer(System.ArraySegment<byte> buffer) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver.CertificateResolver() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.CertificateXmlEncryptor(string! thumbprint, Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver! certificateResolver, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.CertificateXmlEncryptor(System.Security.Cryptography.X509Certificates.X509Certificate2! certificate, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.MachineKey = 32 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.NamedDescriptor = 1 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.None = 0 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.DpapiNGXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.DpapiNGXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor.DpapiNGXmlEncryptor(string! protectionDescriptorRule, Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags flags, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.DpapiXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.DpapiXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor.DpapiXmlEncryptor(bool protectToLocalMachine, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.DecryptorType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.EncryptedElement.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.EncryptedXmlInfo(System.Xml.Linq.XElement! encryptedElement, System.Type! decryptorType) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver
+Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver.ResolveCertificate(string! thumbprint) -> System.Security.Cryptography.X509Certificates.X509Certificate2?
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor.NullXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.NullXmlEncryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.NullXmlEncryptor(System.IServiceProvider? services) -> void
+Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+static Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlExtensions.MarkAsRequiresEncryption(this System.Xml.Linq.XElement! element) -> void
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink! sink) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Func<System.IServiceProvider!, Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink!>! factory) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink<TImplementation>(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyManagementOptions(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Action<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! setupAction) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.DisableAutomaticKeyGeneration(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.PersistKeysToFileSystem(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.IO.DirectoryInfo! directory) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.PersistKeysToRegistry(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.Win32.RegistryKey! registryKey) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! thumbprint) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Security.Cryptography.X509Certificates.X509Certificate2! certificate) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapi(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, bool protectToLocalMachine) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapi(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapiNG(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! protectionDescriptorRule, Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags flags) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapiNG(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.SetApplicationName(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! applicationName) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.SetDefaultKeyLifetime(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.TimeSpan lifetime) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UnprotectKeysWithAnyCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, params System.Security.Cryptography.X509Certificates.X509Certificate2![]! certificates) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseEphemeralDataProtectionProvider(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionUtilityExtensions.GetApplicationUniqueIdentifier(this System.IServiceProvider! services) -> string?
+static Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.DefaultKeyStorageDirectory.get -> System.IO.DirectoryInfo?
+static Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.DefaultRegistryKey.get -> Microsoft.Win32.RegistryKey?
+static Microsoft.AspNetCore.DataProtection.Secret.Random(int numBytes) -> Microsoft.AspNetCore.DataProtection.Secret!
+static Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions.AddDataProtection(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.DataProtection.DataProtectionOptions!>! setupAction) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions.AddDataProtection(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+virtual Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver.ResolveCertificate(string! thumbprint) -> System.Security.Cryptography.X509Certificates.X509Certificate2?

+ 1 - 0
src/DataProtection/DataProtection/src/PublicAPI/net462/PublicAPI.Unshipped.txt

@@ -0,0 +1 @@
+#nullable enable

+ 289 - 0
src/DataProtection/DataProtection/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt

@@ -0,0 +1,289 @@
+#nullable enable
+abstract Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory.AuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory.CngCbcAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CngGcmAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration.AlgorithmConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.AuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.ValidationAlgorithm.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.ValidationAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor.AuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer.AuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.CngCbcAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.HashAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor.CngCbcAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer.CngCbcAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.CngGcmAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.get -> string!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithm.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.get -> string?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.EncryptionAlgorithmProvider.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor.CngGcmAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer.CngGcmAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.get -> int
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmType.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ManagedAuthenticatedEncryptorConfiguration() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ValidationAlgorithmType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.ValidationAlgorithmType.set -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor.ExportToXml() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptor.ManagedAuthenticatedEncryptorDescriptor(Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration! configuration, Microsoft.AspNetCore.DataProtection.ISecret! masterKey) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(System.Xml.Linq.XElement! element) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorDescriptorDeserializer.ManagedAuthenticatedEncryptorDescriptorDeserializer() -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlExtensions
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.DeserializerType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.SerializedDescriptorElement.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlSerializedDescriptorInfo.XmlSerializedDescriptorInfo(System.Xml.Linq.XElement! serializedDescriptorElement, System.Type! deserializerType) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_128_CBC = 0 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_128_GCM = 3 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_192_CBC = 1 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_192_GCM = 4 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_CBC = 2 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_GCM = 5 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.Decrypt(System.ArraySegment<byte> ciphertext, System.ArraySegment<byte> additionalAuthenticatedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor.Encrypt(System.ArraySegment<byte> plaintext, System.ArraySegment<byte> additionalAuthenticatedData) -> byte[]!
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory.CreateEncryptorInstance(Microsoft.AspNetCore.DataProtection.KeyManagement.IKey! key) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ManagedAuthenticatedEncryptorFactory.ManagedAuthenticatedEncryptorFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA256 = 0 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA512 = 1 -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm
+Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.ApplicationDiscriminator.get -> string?
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.ApplicationDiscriminator.set -> void
+Microsoft.AspNetCore.DataProtection.DataProtectionOptions.DataProtectionOptions() -> void
+Microsoft.AspNetCore.DataProtection.DataProtectionUtilityExtensions
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.CreateProtector(string! purpose) -> Microsoft.AspNetCore.DataProtection.IDataProtector!
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.EphemeralDataProtectionProvider() -> void
+Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider.EphemeralDataProtectionProvider(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder
+Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
+Microsoft.AspNetCore.DataProtection.Internal.IActivator
+Microsoft.AspNetCore.DataProtection.Internal.IActivator.CreateInstance(System.Type! expectedBaseType, string! implementationTypeName) -> object!
+Microsoft.AspNetCore.DataProtection.IPersistedDataProtector
+Microsoft.AspNetCore.DataProtection.IPersistedDataProtector.DangerousUnprotect(byte[]! protectedData, bool ignoreRevocationErrors, out bool requiresMigration, out bool wasRevoked) -> byte[]!
+Microsoft.AspNetCore.DataProtection.ISecret
+Microsoft.AspNetCore.DataProtection.ISecret.Length.get -> int
+Microsoft.AspNetCore.DataProtection.ISecret.WriteSecretIntoBuffer(System.ArraySegment<byte> buffer) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager.CanDeleteKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IDeletableKeyManager.DeleteKeys(System.Func<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!, bool>! shouldDelete) -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.ActivationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.CreateEncryptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.CreationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.Descriptor.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.ExpirationDate.get -> System.DateTimeOffset
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.IsRevoked.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKey.KeyId.get -> System.Guid
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink.Store(System.Guid keyId, System.Xml.Linq.XElement! element) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.CreateNewKey(System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.GetAllKeys() -> System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.GetCacheExpirationToken() -> System.Threading.CancellationToken
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.RevokeAllKeys(System.DateTimeOffset revocationDate, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyManager.RevokeKey(System.Guid keyId, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.CacheableKeyRing
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.DefaultKey -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.DefaultKeyResolution() -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.FallbackKey -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution.ShouldGenerateNewKey -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.ICacheableKeyRingProvider
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.ICacheableKeyRingProvider.GetCacheableKeyRing(System.DateTimeOffset now) -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.CacheableKeyRing!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IDefaultKeyResolver
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IDefaultKeyResolver.ResolveDefaultKeyPolicy(System.DateTimeOffset now, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>! allKeys) -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.DefaultKeyResolution
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.CreateNewKey(System.Guid keyId, System.DateTimeOffset creationDate, System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(System.Xml.Linq.XElement! keyElement) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.RevokeSingleKey(System.Guid keyId, System.DateTimeOffset revocationDate, string? reason) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.DefaultAuthenticatedEncryptor.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.DefaultKeyId.get -> System.Guid
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing.GetAuthenticatedEncryptorByKeyId(System.Guid keyId, out bool isRevoked) -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRingProvider
+Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRingProvider.GetCurrentKeyRing() -> Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IKeyRing!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorConfiguration.get -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AlgorithmConfiguration?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorConfiguration.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AuthenticatedEncryptorFactories.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptorFactory!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AutoGenerateKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.AutoGenerateKeys.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.KeyEscrowSinks.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.KeyManagementOptions() -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.NewKeyLifetime.get -> System.TimeSpan
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.NewKeyLifetime.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlEncryptor.get -> Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlEncryptor.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlRepository.get -> Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository?
+Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions.XmlRepository.set -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.CanDeleteKeys.get -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.CreateNewKey(System.DateTimeOffset activationDate, System.DateTimeOffset expirationDate) -> Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.DeleteKeys(System.Func<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!, bool>! shouldDelete) -> bool
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.GetAllKeys() -> System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.KeyManagement.IKey!>!
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.GetCacheExpirationToken() -> System.Threading.CancellationToken
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.RevokeAllKeys(System.DateTimeOffset revocationDate, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.RevokeKey(System.Guid keyId, string? reason = null) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.XmlKeyManager(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! keyManagementOptions, Microsoft.AspNetCore.DataProtection.Internal.IActivator! activator, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.XmlKeyManager(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! keyManagementOptions, Microsoft.AspNetCore.DataProtection.Internal.IActivator! activator) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.Directory.get -> System.IO.DirectoryInfo!
+Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.FileSystemXmlRepository(System.IO.DirectoryInfo! directory, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.DeletionOrder.get -> int?
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.DeletionOrder.set -> void
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement.Element.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.IDeletableXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.RegistryKey.get -> Microsoft.Win32.RegistryKey!
+Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.RegistryXmlRepository(Microsoft.Win32.RegistryKey! registryKey, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.Secret
+Microsoft.AspNetCore.DataProtection.Secret.Dispose() -> void
+Microsoft.AspNetCore.DataProtection.Secret.Length.get -> int
+Microsoft.AspNetCore.DataProtection.Secret.Secret(byte[]! value) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(byte* secret, int secretLength) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(Microsoft.AspNetCore.DataProtection.ISecret! secret) -> void
+Microsoft.AspNetCore.DataProtection.Secret.Secret(System.ArraySegment<byte> value) -> void
+Microsoft.AspNetCore.DataProtection.Secret.WriteSecretIntoBuffer(byte* buffer, int bufferLength) -> void
+Microsoft.AspNetCore.DataProtection.Secret.WriteSecretIntoBuffer(System.ArraySegment<byte> buffer) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver.CertificateResolver() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.CertificateXmlEncryptor(string! thumbprint, Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver! certificateResolver, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.CertificateXmlEncryptor(System.Security.Cryptography.X509Certificates.X509Certificate2! certificate, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.MachineKey = 32 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.NamedDescriptor = 1 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.None = 0 -> Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.DpapiNGXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlDecryptor.DpapiNGXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor.DpapiNGXmlEncryptor(string! protectionDescriptorRule, Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags flags, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.DpapiXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor.DpapiXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor.DpapiXmlEncryptor(bool protectToLocalMachine, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlDecryptor(System.IServiceProvider? services) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.DecryptorType.get -> System.Type!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.EncryptedElement.get -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo.EncryptedXmlInfo(System.Xml.Linq.XElement! encryptedElement, System.Type! decryptorType) -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver
+Microsoft.AspNetCore.DataProtection.XmlEncryption.ICertificateResolver.ResolveCertificate(string! thumbprint) -> System.Security.Cryptography.X509Certificates.X509Certificate2?
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.IXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor.Decrypt(System.Xml.Linq.XElement! encryptedElement) -> System.Xml.Linq.XElement!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlDecryptor.NullXmlDecryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.Encrypt(System.Xml.Linq.XElement! plaintextElement) -> Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlInfo!
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.NullXmlEncryptor() -> void
+Microsoft.AspNetCore.DataProtection.XmlEncryption.NullXmlEncryptor.NullXmlEncryptor(System.IServiceProvider? services) -> void
+Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+override Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration.CreateNewDescriptor() -> Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.IAuthenticatedEncryptorDescriptor!
+static Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.XmlExtensions.MarkAsRequiresEncryption(this System.Xml.Linq.XElement! element) -> void
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink! sink) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Func<System.IServiceProvider!, Microsoft.AspNetCore.DataProtection.KeyManagement.IKeyEscrowSink!>! factory) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyEscrowSink<TImplementation>(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.AddKeyManagementOptions(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Action<Microsoft.AspNetCore.DataProtection.KeyManagement.KeyManagementOptions!>! setupAction) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.DisableAutomaticKeyGeneration(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.PersistKeysToFileSystem(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.IO.DirectoryInfo! directory) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.PersistKeysToRegistry(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.Win32.RegistryKey! registryKey) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! thumbprint) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.Security.Cryptography.X509Certificates.X509Certificate2! certificate) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapi(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, bool protectToLocalMachine) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapi(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapiNG(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! protectionDescriptorRule, Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags flags) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.ProtectKeysWithDpapiNG(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.SetApplicationName(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, string! applicationName) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.SetDefaultKeyLifetime(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, System.TimeSpan lifetime) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UnprotectKeysWithAnyCertificate(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, params System.Security.Cryptography.X509Certificates.X509Certificate2![]! certificates) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngCbcAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.CngGcmAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseCustomCryptographicAlgorithms(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder, Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.ManagedAuthenticatedEncryptorConfiguration! configuration) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.UseEphemeralDataProtectionProvider(this Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder! builder) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.AspNetCore.DataProtection.DataProtectionUtilityExtensions.GetApplicationUniqueIdentifier(this System.IServiceProvider! services) -> string?
+static Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.DefaultKeyStorageDirectory.get -> System.IO.DirectoryInfo?
+static Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.DefaultRegistryKey.get -> Microsoft.Win32.RegistryKey?
+static Microsoft.AspNetCore.DataProtection.Secret.Random(int numBytes) -> Microsoft.AspNetCore.DataProtection.Secret!
+static Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions.AddDataProtection(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.DataProtection.DataProtectionOptions!>! setupAction) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+static Microsoft.Extensions.DependencyInjection.DataProtectionServiceCollectionExtensions.AddDataProtection(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.DeleteElements(System.Action<System.Collections.Generic.IReadOnlyCollection<Microsoft.AspNetCore.DataProtection.Repositories.IDeletableElement!>!>! chooseElements) -> bool
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.GetAllElements() -> System.Collections.Generic.IReadOnlyCollection<System.Xml.Linq.XElement!>!
+virtual Microsoft.AspNetCore.DataProtection.Repositories.RegistryXmlRepository.StoreElement(System.Xml.Linq.XElement! element, string! friendlyName) -> void
+virtual Microsoft.AspNetCore.DataProtection.XmlEncryption.CertificateResolver.ResolveCertificate(string! thumbprint) -> System.Security.Cryptography.X509Certificates.X509Certificate2?

+ 1 - 0
src/DataProtection/DataProtection/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

@@ -0,0 +1 @@
+#nullable enable

+ 3 - 2
src/DataProtection/DataProtection/src/SP800_108/SP800_108_CTR_HMACSHA512Extensions.cs

@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Runtime.CompilerServices;
 using Microsoft.AspNetCore.Cryptography;
 
 namespace Microsoft.AspNetCore.DataProtection.SP800_108;
@@ -24,9 +25,9 @@ internal static unsafe class SP800_108_CTR_HMACSHA512Extensions
 
             fixed (byte* pbContextHeader = contextHeader)
             {
-                UnsafeBufferUtil.BlockCopy(from: pbContextHeader, to: pbCombinedContext, byteCount: contextHeader.Length);
+                Unsafe.CopyBlock(pbCombinedContext, pbContextHeader, (uint)contextHeader.Length);
             }
-            UnsafeBufferUtil.BlockCopy(from: pbContext, to: &pbCombinedContext[contextHeader.Length], byteCount: cbContext);
+            Unsafe.CopyBlock(&pbCombinedContext[contextHeader.Length], pbContext, cbContext);
 
             // At this point, combinedContext := { contextHeader || context }
             provider.DeriveKey(pbLabel, cbLabel, pbCombinedContext, cbCombinedContext, pbDerivedKey, cbDerivedKey);

+ 27 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Aes/AesAuthenticatedEncryptorTests.cs

@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+using Microsoft.AspNetCore.DataProtection.Managed;
+using Microsoft.AspNetCore.DataProtection.Tests.Internal;
+
+namespace Microsoft.AspNetCore.DataProtection.Tests.Aes;
+public class AesAuthenticatedEncryptorTests
+{
+    [Theory]
+    [InlineData(128)]
+    [InlineData(192)]
+    [InlineData(256)]
+    public void Roundtrip_AesGcm_TryEncryptDecrypt_CorrectlyEstimatesDataLength(int symmetricKeySizeBits)
+    {
+        Secret kdk = new Secret(new byte[512 / 8]);
+        IAuthenticatedEncryptor encryptor = new AesGcmAuthenticatedEncryptor(kdk, derivedKeySizeInBytes: symmetricKeySizeBits / 8);
+        ArraySegment<byte> plaintext = new ArraySegment<byte>(Encoding.UTF8.GetBytes("plaintext"));
+        ArraySegment<byte> aad = new ArraySegment<byte>(Encoding.UTF8.GetBytes("aad"));
+
+        RoundtripEncryptionHelpers.AssertTryEncryptTryDecryptParity(encryptor, plaintext, aad);
+    }
+}

+ 2 - 2
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/AuthenticatedEncryption/ConfigurationModel/AuthenticatedEncryptorDescriptorTests.cs

@@ -39,13 +39,13 @@ public class AuthenticatedEncryptorDescriptorTests
             symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_CBC,
             symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8),
             hmacAlgorithmHandle: BCryptAlgorithmHandle.OpenAlgorithmHandle(hashAlgorithm, hmac: true));
-        var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey));
+        var encryptor = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey));
 
         // Act & assert - data round trips properly from control to test
         byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 };
         byte[] aad = new byte[] { 2, 4, 6, 8, 0 };
         byte[] ciphertext = control.Encrypt(new ArraySegment<byte>(plaintext), new ArraySegment<byte>(aad));
-        byte[] roundTripPlaintext = test.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
+        byte[] roundTripPlaintext = encryptor.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
         Assert.Equal(plaintext, roundTripPlaintext);
     }
 

+ 51 - 6
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/CbcAuthenticatedEncryptorTests.cs

@@ -1,11 +1,17 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Buffers;
+using System.Runtime.CompilerServices;
 using System.Security.Cryptography;
 using System.Text;
 using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
 using Microsoft.AspNetCore.DataProtection.Test.Shared;
+using Microsoft.AspNetCore.DataProtection.Tests.Internal;
 using Microsoft.AspNetCore.InternalTesting;
+using Xunit.Abstractions;
 
 namespace Microsoft.AspNetCore.DataProtection.Cng;
 
@@ -93,24 +99,63 @@ public class CbcAuthenticatedEncryptorTests
         ArraySegment<byte> plaintext = new ArraySegment<byte>(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, 2, 3);
         ArraySegment<byte> aad = new ArraySegment<byte>(new byte[] { 7, 6, 5, 4, 3, 2, 1, 0 }, 1, 4);
 
+        var preBufferSize = 3;
+        var postBufferSize = 4;
+
         // Act
         byte[] retVal = encryptor.Encrypt(
             plaintext: plaintext,
             additionalAuthenticatedData: aad,
-            preBufferSize: 3,
-            postBufferSize: 4);
+            preBufferSize: (uint)preBufferSize,
+            postBufferSize: (uint)postBufferSize);
 
         // Assert
 
-        // retVal := 00 00 00 (preBuffer)
+        // retVal := 00 00 00 (preBuffer) (no requirement to have exactly zeros in the preBuffer)
         //         | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F (keyModifier)
         //         | 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F (IV)
         //         | B7 EA 3E 32 58 93 A3 06 03 89 C6 66 03 63 08 4B (encryptedData)
         //         | 9D 8A 85 C7 0F BD 98 D8 7F 72 E7 72 3E B5 A6 26 (HMAC)
         //         | 6C 38 77 F7 66 19 A2 C9 2C BB AD DA E7 62 00 00
-        //         | 00 00 00 00 (postBuffer)
+        //         | 00 00 00 00 (postBuffer) (no requirement to have exactly zeros in the preBuffer)
+
+        Assert.Equal(80 + preBufferSize + postBufferSize, retVal.Length);
+
+        var buffer = retVal.AsSpan(preBufferSize, retVal.Length - preBufferSize - postBufferSize);
+        var retValAsString = Convert.ToBase64String(buffer);
+        Assert.Equal("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+36j4yWJOjBgOJxmYDYwhLnYqFxw+9mNh/cudyPrWmJmw4d/dmGaLJLLut2udiAAA=", retValAsString);
+    }
+
+    [ConditionalTheory]
+    [ConditionalRunTestOnlyOnWindows]
+    [InlineData(128, "SHA256", "")]
+    [InlineData(128, "SHA256", "This is a small text")]
+    [InlineData(128, "SHA256", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    [InlineData(192, "SHA256", "")]
+    [InlineData(192, "SHA256", "This is a small text")]
+    [InlineData(192, "SHA256", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    [InlineData(256, "SHA256", "")]
+    [InlineData(256, "SHA256", "This is a small text")]
+    [InlineData(256, "SHA256", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    [InlineData(128, "SHA512", "")]
+    [InlineData(128, "SHA512", "This is a small text")]
+    [InlineData(128, "SHA512", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    [InlineData(192, "SHA512", "")]
+    [InlineData(192, "SHA512", "This is a small text")]
+    [InlineData(192, "SHA512", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    [InlineData(256, "SHA512", "")]
+    [InlineData(256, "SHA512", "This is a small text")]
+    [InlineData(256, "SHA512", "This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly")]
+    public void Roundtrip_TryEncryptDecrypt_CorrectlyEstimatesDataLength(int symmetricKeySizeBits, string hmacAlgorithm, string plainText)
+    {
+        Secret kdk = new Secret(new byte[512 / 8]);
+        IAuthenticatedEncryptor encryptor = new CbcAuthenticatedEncryptor(kdk,
+            symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_CBC,
+            symmetricAlgorithmKeySizeInBytes: (uint)(symmetricKeySizeBits / 8),
+            hmacAlgorithmHandle: BCryptAlgorithmHandle.OpenAlgorithmHandle(hmacAlgorithm, hmac: true));
+        ArraySegment<byte> plaintext = new ArraySegment<byte>(Encoding.UTF8.GetBytes(plainText));
+        ArraySegment<byte> aad = new ArraySegment<byte>(Encoding.UTF8.GetBytes("aad"));
 
-        string retValAsString = Convert.ToBase64String(retVal);
-        Assert.Equal("AAAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+36j4yWJOjBgOJxmYDYwhLnYqFxw+9mNh/cudyPrWmJmw4d/dmGaLJLLut2udiAAAAAAAA", retValAsString);
+        RoundtripEncryptionHelpers.AssertTryEncryptTryDecryptParity(encryptor, plaintext, aad);
     }
 }

+ 0 - 105
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/CngAuthenticatedEncryptorBaseTests.cs

@@ -1,105 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Moq;
-
-namespace Microsoft.AspNetCore.DataProtection.Cng.Internal;
-
-public unsafe class CngAuthenticatedEncryptorBaseTests
-{
-    [Fact]
-    public void Decrypt_ForwardsArraySegment()
-    {
-        // Arrange
-        var ciphertext = new ArraySegment<byte>(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, 3, 2);
-        var aad = new ArraySegment<byte>(new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14 }, 1, 4);
-
-        var encryptorMock = new Mock<MockableEncryptor>();
-        encryptorMock
-            .Setup(o => o.DecryptHook(It.IsAny<IntPtr>(), 2, It.IsAny<IntPtr>(), 4))
-            .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) =>
-            {
-                // ensure that pointers started at the right place
-                Assert.Equal((byte)0x03, *(byte*)pbCiphertext);
-                Assert.Equal((byte)0x11, *(byte*)pbAdditionalAuthenticatedData);
-                return new byte[] { 0x20, 0x21, 0x22 };
-            });
-
-        // Act
-        var retVal = encryptorMock.Object.Decrypt(ciphertext, aad);
-
-        // Assert
-        Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal);
-    }
-
-    [Fact]
-    public void Decrypt_HandlesEmptyAADPointerFixup()
-    {
-        // Arrange
-        var ciphertext = new ArraySegment<byte>(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, 3, 2);
-        var aad = new ArraySegment<byte>(new byte[0]);
-
-        var encryptorMock = new Mock<MockableEncryptor>();
-        encryptorMock
-            .Setup(o => o.DecryptHook(It.IsAny<IntPtr>(), 2, It.IsAny<IntPtr>(), 0))
-            .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) =>
-            {
-                // ensure that pointers started at the right place
-                Assert.Equal((byte)0x03, *(byte*)pbCiphertext);
-                Assert.NotEqual(IntPtr.Zero, pbAdditionalAuthenticatedData); // CNG will complain if this pointer is zero
-                return new byte[] { 0x20, 0x21, 0x22 };
-            });
-
-        // Act
-        var retVal = encryptorMock.Object.Decrypt(ciphertext, aad);
-
-        // Assert
-        Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal);
-    }
-
-    [Fact]
-    public void Decrypt_HandlesEmptyCiphertextPointerFixup()
-    {
-        // Arrange
-        var ciphertext = new ArraySegment<byte>(new byte[0]);
-        var aad = new ArraySegment<byte>(new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14 }, 1, 4);
-
-        var encryptorMock = new Mock<MockableEncryptor>();
-        encryptorMock
-            .Setup(o => o.DecryptHook(It.IsAny<IntPtr>(), 0, It.IsAny<IntPtr>(), 4))
-            .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) =>
-            {
-                // ensure that pointers started at the right place
-                Assert.NotEqual(IntPtr.Zero, pbCiphertext); // CNG will complain if this pointer is zero
-                Assert.Equal((byte)0x11, *(byte*)pbAdditionalAuthenticatedData);
-                return new byte[] { 0x20, 0x21, 0x22 };
-            });
-
-        // Act
-        var retVal = encryptorMock.Object.Decrypt(ciphertext, aad);
-
-        // Assert
-        Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal);
-    }
-
-    internal abstract class MockableEncryptor : CngAuthenticatedEncryptorBase
-    {
-        public override void Dispose()
-        {
-        }
-
-        public abstract byte[] DecryptHook(IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData);
-
-        protected sealed override unsafe byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
-        {
-            return DecryptHook((IntPtr)pbCiphertext, cbCiphertext, (IntPtr)pbAdditionalAuthenticatedData, cbAdditionalAuthenticatedData);
-        }
-
-        public abstract byte[] EncryptHook(IntPtr pbPlaintext, uint cbPlaintext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer);
-
-        protected sealed override unsafe byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer)
-        {
-            return EncryptHook((IntPtr)pbPlaintext, cbPlaintext, (IntPtr)pbAdditionalAuthenticatedData, cbAdditionalAuthenticatedData, cbPreBuffer, cbPostBuffer);
-        }
-    }
-}

+ 20 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Cng/GcmAuthenticatedEncryptorTests.cs

@@ -4,7 +4,10 @@
 using System.Security.Cryptography;
 using System.Text;
 using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+using Microsoft.AspNetCore.DataProtection.Managed;
 using Microsoft.AspNetCore.DataProtection.Test.Shared;
+using Microsoft.AspNetCore.DataProtection.Tests.Internal;
 using Microsoft.AspNetCore.InternalTesting;
 
 namespace Microsoft.AspNetCore.DataProtection.Cng;
@@ -102,4 +105,21 @@ public class GcmAuthenticatedEncryptorTests
         string retValAsString = Convert.ToBase64String(retVal);
         Assert.Equal("AAAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaG0O2kY0NZtmh2UQtXY5B2jlgnOgAAAAA", retValAsString);
     }
+
+    [ConditionalTheory]
+    [ConditionalRunTestOnlyOnWindows]
+    [InlineData(128)]
+    [InlineData(192)]
+    [InlineData(256)]
+    public void Roundtrip_CngGcm_TryEncryptDecrypt_CorrectlyEstimatesDataLength(int symmetricKeySizeBits)
+    {
+        Secret kdk = new Secret(new byte[512 / 8]);
+        IAuthenticatedEncryptor encryptor = new CngGcmAuthenticatedEncryptor(kdk,
+            symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_GCM,
+            symmetricAlgorithmKeySizeInBytes: (uint)(symmetricKeySizeBits / 8));
+        ArraySegment<byte> plaintext = new ArraySegment<byte>(Encoding.UTF8.GetBytes("plaintext"));
+        ArraySegment<byte> aad = new ArraySegment<byte>(Encoding.UTF8.GetBytes("aad"));
+
+        RoundtripEncryptionHelpers.AssertTryEncryptTryDecryptParity(encryptor, plaintext, aad);
+    }
 }

+ 91 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Internal/RoundtripEncryptionHelpers.cs

@@ -0,0 +1,91 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+
+namespace Microsoft.AspNetCore.DataProtection.Tests.Internal;
+
+internal static class RoundtripEncryptionHelpers
+{
+    /// <summary>
+    /// <see cref="ISpanAuthenticatedEncryptor.TryEncrypt"/> and <see cref="ISpanAuthenticatedEncryptor.TryDecrypt"/> APIs should do the same steps
+    /// as <see cref="IAuthenticatedEncryptor.Encrypt"/> and <see cref="IAuthenticatedEncryptor.Decrypt"/> APIs.
+    /// <br/>
+    /// Method ensures that the two APIs are equivalent in terms of their behavior by performing a roundtrip encrypt-decrypt test.
+    /// </summary>
+    public static void AssertTryEncryptTryDecryptParity(IAuthenticatedEncryptor encryptor, ArraySegment<byte> plaintext, ArraySegment<byte> aad)
+    {
+        var spanAuthenticatedEncryptor = encryptor as ISpanAuthenticatedEncryptor;
+        Debug.Assert(spanAuthenticatedEncryptor != null, "ISpanAuthenticatedEncryptor is not supported by the encryptor");
+
+        // assert "allocatey" Encrypt/Decrypt APIs roundtrip correctly
+        byte[] ciphertext = encryptor.Encrypt(plaintext, aad);
+        byte[] decipheredtext = encryptor.Decrypt(new ArraySegment<byte>(ciphertext), aad);
+        Assert.Equal(plaintext.AsSpan(), decipheredtext.AsSpan());
+
+        // perform TryEncrypt and Decrypt roundtrip - ensures cross operation compatibility
+        var buffer = new ArrayBufferWriter<byte>();
+        spanAuthenticatedEncryptor.Encrypt(plaintext, aad, ref buffer);
+        var encryptResult = buffer.WrittenSpan.ToArray();
+        Assert.Equal(ciphertext.Length, encryptResult.Length);
+        // we can't sequence equal here, because the ciphertext will differ due to random IVs
+
+        buffer = new ArrayBufferWriter<byte>();
+        spanAuthenticatedEncryptor.Decrypt(encryptResult, aad, ref buffer);
+        var decryptedResult = buffer.WrittenSpan.ToArray();
+        Assert.Equal(decipheredtext.Length, decryptedResult.Length);
+        Assert.True(decryptedResult.SequenceEqual(decipheredtext));
+
+        // perform Encrypt and TryDecrypt roundtrip - ensures cross operation compatibility
+        var encrypted = spanAuthenticatedEncryptor.Encrypt(plaintext, aad);
+
+        buffer = new ArrayBufferWriter<byte>();
+        spanAuthenticatedEncryptor.Decrypt(encrypted, aad, ref buffer);
+        var decryptedResult2 = buffer.WrittenSpan;
+        Assert.Equal(decipheredtext.Length, decryptedResult2.Length);
+        Assert.True(decryptedResult2.SequenceEqual(decipheredtext));
+    }
+
+    /// <summary>
+    /// <see cref="ISpanDataProtector.TryProtect"/> and <see cref="ISpanDataProtector.TryUnprotect"/> APIs should do the same steps
+    /// as <see cref="IDataProtector.Protect"/> and <see cref="IDataProtector.Unprotect"/> APIs.
+    /// <br/>
+    /// Method ensures that the two APIs are equivalent in terms of their behavior by performing a roundtrip protect-unprotect test.
+    /// </summary>
+    public static void AssertTryProtectTryUnprotectParity(ISpanDataProtector protector, ReadOnlySpan<byte> plaintext)
+    {
+        // assert "allocatey" Protect/Unprotect APIs roundtrip correctly
+        byte[] protectedData = protector.Protect(plaintext.ToArray());
+        byte[] unprotectedData = protector.Unprotect(protectedData);
+        Assert.Equal(plaintext, unprotectedData.AsSpan());
+
+        // perform TryProtect and Unprotect roundtrip - ensures cross operation compatibility
+        var buffer = new ArrayBufferWriter<byte>();
+        protector.Protect(plaintext, ref buffer);
+        var protectedResult = buffer.WrittenSpan;
+        Assert.Equal(protectedData.Length, protectedResult.Length);
+        // we can't sequence equal here, because the ciphertext will differ due to random IVs
+
+        buffer = new ArrayBufferWriter<byte>();
+        protector.Unprotect(protectedResult, ref buffer);
+        var unProtectedResult = buffer.WrittenSpan;
+        Assert.Equal(unprotectedData.Length, unProtectedResult.Length);
+        Assert.True(unProtectedResult.SequenceEqual(unprotectedData));
+
+        // perform Protect and TryUnprotect roundtrip - ensures cross operation compatibility
+        // Note: This test is limited because we can't easily access the correct AAD from outside the protector
+        // But we can test basic functionality with empty AAD and expect it to fail gracefully
+        var protectedByProtect = protector.Protect(plaintext.ToArray());
+
+        buffer = new ArrayBufferWriter<byte>();
+        protector.Unprotect(protectedByProtect, ref buffer);
+        var unProtectedResult2 = buffer.WrittenSpan;
+        Assert.Equal(unprotectedData.Length, unProtectedResult.Length);
+        Assert.True(unProtectedResult.SequenceEqual(unprotectedData));
+    }
+}

+ 157 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingBasedDataProtectorTests.cs

@@ -1,12 +1,18 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Diagnostics;
 using System.Globalization;
 using System.Net;
+using System.Security.Cryptography;
 using System.Text;
 using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
 using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
 using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
+using Microsoft.AspNetCore.DataProtection.Managed;
+using Microsoft.AspNetCore.DataProtection.Tests.Internal;
 using Microsoft.AspNetCore.InternalTesting;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
@@ -619,6 +625,157 @@ public class KeyRingBasedDataProtectorTests
         Assert.Equal(expectedProtectedData, retVal);
     }
 
+    [Theory]
+    [InlineData("", EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("small", EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("This is a medium length plaintext message", EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("This is a very long plaintext message that spans multiple blocks and should test the encryption and size estimation with larger payloads to ensure everything works correctly", EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("small", EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("This is a medium length plaintext message", EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA512)]
+    [InlineData("small", EncryptionAlgorithm.AES_128_GCM, ValidationAlgorithm.HMACSHA256)]
+    [InlineData("This is a medium length plaintext message", EncryptionAlgorithm.AES_256_GCM, ValidationAlgorithm.HMACSHA256)]
+    public void GetProtectedSize_TryProtectUnprotect_CorrectlyEstimatesDataLength_MultipleScenarios(string plaintextStr, EncryptionAlgorithm encryptionAlgorithm, ValidationAlgorithm validationAlgorithm)
+    {
+        byte[] plaintext = Encoding.UTF8.GetBytes(plaintextStr);
+        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
+        
+        var configuration = new AuthenticatedEncryptorConfiguration
+        {
+            EncryptionAlgorithm = encryptionAlgorithm,
+            ValidationAlgorithm = validationAlgorithm
+        };
+        
+        Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, configuration.CreateNewDescriptor(), new[] { encryptorFactory });
+        var keyRing = new KeyRing(key, [ key ]);
+        var mockKeyRingProvider = new Mock<IKeyRingProvider>();
+        mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
+
+        var protector = new KeyRingBasedSpanDataProtector(
+            keyRingProvider: mockKeyRingProvider.Object,
+            logger: GetLogger(),
+            originalPurposes: null,
+            newPurpose: "purpose");
+
+        RoundtripEncryptionHelpers.AssertTryProtectTryUnprotectParity(protector, plaintext);
+    }
+
+    [Theory]
+    [InlineData(16)]     // 16 bytes
+    [InlineData(32)]     // 32 bytes  
+    [InlineData(64)]     // 64 bytes
+    [InlineData(128)]    // 128 bytes
+    [InlineData(256)]    // 256 bytes
+    [InlineData(512)]    // 512 bytes
+    [InlineData(1024)]   // 1 KB
+    [InlineData(4096)]   // 4 KB
+    public void GetProtectedSize_TryProtect_VariousPlaintextSizes(int plaintextSize)
+    {
+        byte[] plaintext = new byte[plaintextSize];
+        for (int i = 0; i < plaintextSize; i++)
+        {
+            plaintext[i] = (byte)(i % 256);
+        }
+
+        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
+        Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration().CreateNewDescriptor(), new[] { encryptorFactory });
+        var keyRing = new KeyRing(key, new[] { key });
+        var mockKeyRingProvider = new Mock<IKeyRingProvider>();
+        mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
+
+        var protector = new KeyRingBasedSpanDataProtector(
+            keyRingProvider: mockKeyRingProvider.Object,
+            logger: GetLogger(),
+            originalPurposes: null,
+            newPurpose: "purpose");
+
+        RoundtripEncryptionHelpers.AssertTryProtectTryUnprotectParity(protector, plaintext);
+    }
+
+    [Theory]
+    [InlineData(16)]     // 16 bytes
+    [InlineData(32)]     // 32 bytes  
+    [InlineData(64)]     // 64 bytes
+    [InlineData(128)]    // 128 bytes
+    [InlineData(256)]    // 256 bytes
+    [InlineData(512)]    // 512 bytes
+    [InlineData(1024)]   // 1 KB
+    [InlineData(4096)]   // 4 KB
+    public void GetUnprotectedSize_EstimatesCorrectly_VariousPlaintextSizes(int plaintextSize)
+    {
+        // Arrange
+        byte[] plaintext = new byte[plaintextSize];
+        // Fill with a pattern to make debugging easier if needed
+        for (int i = 0; i < plaintextSize; i++)
+        {
+            plaintext[i] = (byte)(i % 256);
+        }
+
+        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
+        Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration().CreateNewDescriptor(), new[] { encryptorFactory });
+        var keyRing = new KeyRing(key, new[] { key });
+        var mockKeyRingProvider = new Mock<IKeyRingProvider>();
+        mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
+
+        var protector = new KeyRingBasedSpanDataProtector(
+            keyRingProvider: mockKeyRingProvider.Object,
+            logger: GetLogger(),
+            originalPurposes: null,
+            newPurpose: "purpose");
+
+        // Act - first protect the data  
+        byte[] protectedData = protector.Protect(plaintext);
+
+        var arrayBufferWriter = new ArrayBufferWriter<byte>(plaintextSize);
+        protector.Unprotect(protectedData, ref arrayBufferWriter);
+        var unprotectedData = arrayBufferWriter.WrittenSpan;
+        Assert.Equal(plaintextSize, unprotectedData.Length);
+        Assert.Equal(plaintext, unprotectedData);
+    }
+
+    [Fact]
+    public void TryUnprotect_WithTooShortCiphertext_ReturnsFalse()
+    {
+        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
+        Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration().CreateNewDescriptor(), new[] { encryptorFactory });
+        var keyRing = new KeyRing(key, new[] { key });
+        var mockKeyRingProvider = new Mock<IKeyRingProvider>();
+        mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
+
+        var protector = new KeyRingBasedSpanDataProtector(
+            keyRingProvider: mockKeyRingProvider.Object,
+            logger: GetLogger(),
+            originalPurposes: null,
+            newPurpose: "purpose");
+
+        // Act - try to unprotect with too short ciphertext (shorter than magic header + key id)
+        byte[] shortCiphertext = new byte[10]; // Less than 20 bytes (magic header + key id)
+        var destination = new ArrayBufferWriter<byte>(100);
+
+        var ex = ExceptionAssert2.ThrowsCryptographicException(() => protector.Unprotect(shortCiphertext, ref destination));
+        Assert.Equal(Resources.ProtectionProvider_BadMagicHeader, ex.Message);
+    }
+
+    [Fact]
+    public void GetUnprotectedSize_WithTooShortCiphertext_ThrowsException()
+    {
+        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
+        Key key = new Key(Guid.NewGuid(), DateTimeOffset.Now, DateTimeOffset.Now, DateTimeOffset.Now, new AuthenticatedEncryptorConfiguration().CreateNewDescriptor(), new[] { encryptorFactory });
+        var keyRing = new KeyRing(key, [ key ]);
+        var mockKeyRingProvider = new Mock<IKeyRingProvider>();
+        mockKeyRingProvider.Setup(o => o.GetCurrentKeyRing()).Returns(keyRing);
+
+        var protector = new KeyRingBasedSpanDataProtector(
+            keyRingProvider: mockKeyRingProvider.Object,
+            logger: GetLogger(),
+            originalPurposes: null,
+            newPurpose: "purpose");
+
+        // Less than magic header + key id size
+        var buffer = new ArrayBufferWriter<byte>();
+        var ex = ExceptionAssert2.ThrowsCryptographicException(() => protector.Unprotect(new byte[10], ref buffer));
+        Assert.Equal(Resources.ProtectionProvider_BadMagicHeader, ex.Message);
+    }
+
     private static byte[] BuildAadFromPurposeStrings(Guid keyId, params string[] purposes)
     {
         var expectedAad = new byte[] { 0x09, 0xF0, 0xC9, 0xF0 } // magic header

+ 66 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Managed/ManagedAuthenticatedEncryptorTests.cs

@@ -1,8 +1,15 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Buffers;
 using System.Security.Cryptography;
 using System.Text;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
+using Microsoft.AspNetCore.DataProtection.Tests;
+using Microsoft.AspNetCore.DataProtection.Tests.Internal;
+using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.Extensions.Logging.Abstractions;
 
 namespace Microsoft.AspNetCore.DataProtection.Managed;
 
@@ -103,4 +110,63 @@ public class ManagedAuthenticatedEncryptorTests
         string retValAsString = Convert.ToBase64String(retVal);
         Assert.Equal("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+36j4yWJOjBgOJxmYDYwhLnYqFxw+9mNh/cudyPrWmJmw4d/dmGaLJLLut2udiAAA=", retValAsString);
     }
+
+    [Theory]
+    [InlineData(128, "SHA256")]
+    [InlineData(192, "SHA256")]
+    [InlineData(256, "SHA256")]
+    [InlineData(128, "SHA512")]
+    [InlineData(192, "SHA512")]
+    [InlineData(256, "SHA512")]
+    public void Roundtrip_TryEncryptDecrypt_CorrectlyEstimatesDataLength(int symmetricKeySizeBits, string hmacAlgorithm)
+    {
+        Secret kdk = new Secret(new byte[512 / 8]);
+
+        Func<KeyedHashAlgorithm> validationAlgorithmFactory = hmacAlgorithm switch
+        {
+            "SHA256" => () => new HMACSHA256(),
+            "SHA512" => () => new HMACSHA512(),
+            _ => throw new ArgumentException($"Unsupported HMAC algorithm: {hmacAlgorithm}")
+        };
+
+        IAuthenticatedEncryptor encryptor = new ManagedAuthenticatedEncryptor(kdk,
+            symmetricAlgorithmFactory: Aes.Create,
+            symmetricAlgorithmKeySizeInBytes: symmetricKeySizeBits / 8,
+            validationAlgorithmFactory: validationAlgorithmFactory);
+
+        ArraySegment<byte> plaintext = new ArraySegment<byte>(Encoding.UTF8.GetBytes("plaintext"));
+        ArraySegment<byte> aad = new ArraySegment<byte>(Encoding.UTF8.GetBytes("aad"));
+
+        RoundtripEncryptionHelpers.AssertTryEncryptTryDecryptParity(encryptor, plaintext, aad);
+    }
+
+    [Fact]
+    public void TimeLimitedDataProtector_WithJsonPayloadNearBufferBoundary_SucceedsWithoutBufferException()
+    {
+        // This test reproduces the issue from ServerComponentDeserializerTest.DoesNotParseMarkersWithUnknownComponentTypeAssembly
+        // which uses ITimeLimitedDataProtector with ManagedAuthenticatedEncryptor under the hood.
+        // The buffer boundary condition occurs when the output size calculation results in a value
+        // that is close to or exceeds 255 bytes (the initial stackalloc size).
+
+        // Arrange
+        var dataProtectionProvider = new TestsDataProtectionProvider<ManagedAuthenticatedEncryptorConfiguration>();
+        var protector = dataProtectionProvider
+            .CreateProtector("test-purpose")
+            .ToTimeLimitedDataProtector();
+
+        // The exact JSON payload from the failing test scenario
+        var jsonPayload = @"{""sequence"":0,""assemblyName"":""UnknownAssembly"",""typeName"":""System.String"",""parameterDefinitions"":[],""parameterValues"":[],""invocationId"":""0db30695-0ec3-4289-b3cd-247cbe91ccc3""}";
+
+        // Act - This should not throw "Cannot advance past the end of the buffer"
+        var protectedData = protector.Protect(jsonPayload, TimeSpan.FromSeconds(30));
+
+        // Assert
+        Assert.NotNull(protectedData);
+        Assert.NotEmpty(protectedData);
+
+        // Verify the round-trip works
+        var unprotectedData = protector.Unprotect(protectedData, out var expiration);
+        Assert.Equal(jsonPayload, unprotectedData);
+        Assert.True(expiration > DateTimeOffset.UtcNow, "Expiration should be in the future");
+    }
 }

+ 3 - 2
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Microsoft.AspNetCore.DataProtection.Tests.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -14,11 +14,12 @@
     <Reference Include="Microsoft.AspNetCore.Hosting" />
     <Reference Include="Microsoft.AspNetCore.Cryptography.Internal" />
     <Reference Include="Microsoft.AspNetCore.DataProtection" />
+    <Reference Include="Microsoft.AspNetCore.DataProtection.Extensions" />
     <Reference Include="Microsoft.Extensions.DependencyInjection" />
     <Reference Include="Microsoft.Extensions.Hosting" />
   </ItemGroup>
 
   <ItemGroup>
     <InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
-  </ItemGroup>
+  </ItemGroup>  
 </Project>

+ 98 - 0
src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/TestsDataProtectionProvider.cs

@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
+using Microsoft.AspNetCore.Shared;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Microsoft.AspNetCore.DataProtection.Tests;
+
+// use T = CngGcmAuthenticatedEncryptorConfiguration; for fastest implementation: AES-256-GCM [CNG]
+// use T = ManagedAuthenticatedEncryptorConfiguration; for slowest implementation: AES-256-CBC + HMACSHA256 [Managed]
+internal class TestsDataProtectionProvider<T> : IDataProtectionProvider
+    where T : AlgorithmConfiguration, new()
+{
+    private readonly KeyRingBasedDataProtectionProvider _dataProtectionProvider;
+
+    public TestsDataProtectionProvider() : this(NullLoggerFactory.Instance)
+    {
+    }
+
+    public TestsDataProtectionProvider(ILoggerFactory loggerFactory)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(loggerFactory);
+
+        IKeyRingProvider keyringProvider = new EphemeralKeyRing(loggerFactory);
+        var logger = loggerFactory.CreateLogger<EphemeralDataProtectionProvider>();
+        logger.UsingEphemeralDataProtectionProvider();
+
+        _dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyringProvider, loggerFactory);
+    }
+
+    /// <inheritdoc />
+    public IDataProtector CreateProtector(string purpose)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(purpose);
+
+        // just forward to the underlying provider
+        return _dataProtectionProvider.CreateProtector(purpose);
+    }
+
+    private sealed class EphemeralKeyRing : IKeyRing, IKeyRingProvider
+    {
+        public EphemeralKeyRing(ILoggerFactory loggerFactory)
+        {
+            DefaultAuthenticatedEncryptor = GetDefaultEncryptor(loggerFactory);
+        }
+
+        public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; }
+
+        public Guid DefaultKeyId { get; }
+
+        public IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
+        {
+            isRevoked = false;
+            return (keyId == default(Guid)) ? DefaultAuthenticatedEncryptor : null;
+        }
+
+        public IKeyRing GetCurrentKeyRing()
+        {
+            return this;
+        }
+
+        private static IAuthenticatedEncryptor GetDefaultEncryptor(ILoggerFactory loggerFactory)
+        {
+            var configuration = new T();
+            if (configuration is CngGcmAuthenticatedEncryptorConfiguration cngConfiguration)
+            {
+                Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+                var descriptor = (CngGcmAuthenticatedEncryptorDescriptor)new T().CreateNewDescriptor();
+                return new CngGcmAuthenticatedEncryptorFactory(loggerFactory)
+                    .CreateAuthenticatedEncryptorInstance(
+                        descriptor.MasterKey,
+                        cngConfiguration);
+            }
+            else if (configuration is ManagedAuthenticatedEncryptorConfiguration managedConfiguration)
+            {
+                var descriptor = (ManagedAuthenticatedEncryptorDescriptor)new T().CreateNewDescriptor();
+                return new ManagedAuthenticatedEncryptorFactory(loggerFactory)
+                    .CreateAuthenticatedEncryptorInstance(
+                        descriptor.MasterKey,
+                        managedConfiguration);
+            }
+
+            throw new NotSupportedException($"Such type of Encryptor is not supported: {typeof(T)}");
+        }
+    }
+}

+ 6 - 5
src/DataProtection/Extensions/src/TimeLimitedDataProtector.cs

@@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.DataProtection;
 internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
 {
     private const string MyPurposeString = "Microsoft.AspNetCore.DataProtection.TimeLimitedDataProtector.v1";
+    private const int ExpirationTimeHeaderSize = 8; // size of the expiration time header in bytes (64-bit UTC tick count)
 
     private readonly IDataProtector _innerProtector;
     private IDataProtector? _innerProtectorWithTimeLimitedPurpose; // created on-demand
@@ -50,9 +51,9 @@ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
         ArgumentNullThrowHelper.ThrowIfNull(plaintext);
 
         // We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
-        byte[] plaintextWithHeader = new byte[checked(8 + plaintext.Length)];
+        byte[] plaintextWithHeader = new byte[checked(ExpirationTimeHeaderSize + plaintext.Length)];
         BitHelpers.WriteUInt64(plaintextWithHeader, 0, (ulong)expiration.UtcTicks);
-        Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, 8, plaintext.Length);
+        Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, ExpirationTimeHeaderSize, plaintext.Length);
 
         return GetInnerProtectorWithTimeLimitedPurpose().Protect(plaintextWithHeader);
     }
@@ -71,7 +72,7 @@ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
         try
         {
             byte[] plaintextWithHeader = GetInnerProtectorWithTimeLimitedPurpose().Unprotect(protectedData);
-            if (plaintextWithHeader.Length < 8)
+            if (plaintextWithHeader.Length < ExpirationTimeHeaderSize)
             {
                 // header isn't present
                 throw new CryptographicException(Resources.TimeLimitedDataProtector_PayloadInvalid);
@@ -88,8 +89,8 @@ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
             }
 
             // Not expired - split and return payload
-            byte[] retVal = new byte[plaintextWithHeader.Length - 8];
-            Buffer.BlockCopy(plaintextWithHeader, 8, retVal, 0, retVal.Length);
+            byte[] retVal = new byte[plaintextWithHeader.Length - ExpirationTimeHeaderSize];
+            Buffer.BlockCopy(plaintextWithHeader, ExpirationTimeHeaderSize, retVal, 0, retVal.Length);
             expiration = embeddedExpiration;
             return retVal;
         }

+ 4 - 0
src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/AssemblyInfo.cs

@@ -0,0 +1,4 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]

+ 134 - 0
src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/Benchmarks/SpanDataProtectorComparison.cs

@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using BenchmarkDotNet.Attributes;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection.MicroBenchmarks.Benchmarks;
+
+/*
+
+    BenchmarkDotNet=v0.13.0, OS=Windows 10.0.26100
+    AMD Ryzen 9 7950X3D, 1 CPU, 32 logical and 16 physical cores
+    .NET SDK=10.0.100-rc.1.25420.111
+      [Host]     : .NET 10.0.0 (10.0.25.42121), X64 RyuJIT
+      DefaultJob : .NET 10.0.0 (10.0.25.42111), X64 RyuJIT
+      Job-UEQIYD : .NET 10.0.0 (10.0.25.42111), X64 RyuJIT
+
+    Server=True
+
+### Numbers before improving the code flow (only span<byte> API related changes)
+|                                 Method |        Job |      Toolchain | RunStrategy | PlaintextLength |     Mean |     Error |    StdDev |   Median |      Op/s |  Gen 0 | Gen 1 | Gen 2 | Allocated |
+|--------------------------------------- |----------- |--------------- |------------ |---------------- |---------:|----------:|----------:|---------:|----------:|-------:|------:|------:|----------:|
+|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 4.097 us | 0.0598 us | 0.0530 us | 4.079 us | 244,090.2 |      - |     - |     - |     360 B |
+| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 3.886 us | 0.0211 us | 0.0176 us | 3.880 us | 257,339.0 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 3.844 us | 0.0507 us | 0.0423 us | 3.823 us | 260,122.2 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 4.178 us | 0.0825 us | 0.1356 us | 4.134 us | 239,344.6 |      - |     - |     - |     360 B |
+| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 3.902 us | 0.0258 us | 0.0202 us | 3.896 us | 256,296.9 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 3.943 us | 0.0635 us | 0.0731 us | 3.909 us | 253,595.7 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.230 us | 0.0809 us | 0.0831 us | 4.190 us | 236,415.8 | 0.0076 |     - |     - |     456 B |
+| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.019 us | 0.0734 us | 0.0816 us | 3.991 us | 248,798.9 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.036 us | 0.0802 us | 0.1778 us | 3.971 us | 247,794.0 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 4.244 us | 0.0839 us | 0.0744 us | 4.208 us | 235,623.3 |      - |     - |     - |     456 B |
+| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 4.000 us | 0.0889 us | 0.2579 us | 4.005 us | 249,994.1 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 3.679 us | 0.0692 us | 0.0740 us | 3.654 us | 271,839.1 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.862 us | 0.0741 us | 0.0728 us | 3.857 us | 258,957.3 | 0.0076 |     - |     - |     512 B |
+| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.725 us | 0.0743 us | 0.1242 us | 3.677 us | 268,484.2 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.727 us | 0.0745 us | 0.1539 us | 3.665 us | 268,320.4 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 4.039 us | 0.0805 us | 0.2078 us | 3.939 us | 247,555.9 |      - |     - |     - |     512 B |
+| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 3.809 us | 0.0751 us | 0.0835 us | 3.783 us | 262,504.4 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 3.718 us | 0.0711 us | 0.0665 us | 3.717 us | 268,936.9 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 4.086 us | 0.0796 us | 0.2054 us | 4.011 us | 244,726.7 | 0.0076 |     - |     - |     552 B |
+| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 3.922 us | 0.0773 us | 0.1613 us | 3.877 us | 254,955.2 | 0.0038 |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 3.658 us | 0.0521 us | 0.0435 us | 3.658 us | 273,341.9 |      - |     - |     - |     160 B |
+|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 4.088 us | 0.0805 us | 0.1018 us | 4.046 us | 244,641.5 |      - |     - |     - |     552 B |
+| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 3.800 us | 0.0351 us | 0.0293 us | 3.805 us | 263,132.8 |      - |     - |     - |     224 B |
+|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 3.797 us | 0.0735 us | 0.1031 us | 3.752 us | 263,344.2 |      - |     - |     - |     160 B |
+
+### 
+
+*/
+
+[SimpleJob, MemoryDiagnoser]
+public class SpanDataProtectorComparison
+{
+    private IDataProtector _dataProtector = null!;
+    private ISpanDataProtector _spanDataProtector = null!;
+
+    private byte[] _plaintext = null!;
+
+    [Params(5, 50, 80, 100)]
+    public int PlaintextLength { get; set; }
+
+    [GlobalSetup]
+    public void Setup()
+    {
+        // Setup DataProtection as in DI
+        var services = new ServiceCollection();
+        services.AddDataProtection();
+        var serviceProvider = services.BuildServiceProvider();
+
+        _dataProtector = serviceProvider.GetDataProtector("benchmark", "test");
+        _spanDataProtector = (ISpanDataProtector)_dataProtector;
+
+        // Setup test data for different lengths
+        var random = new Random(42); // Fixed seed for consistent results
+
+        _plaintext = new byte[PlaintextLength];
+        random.NextBytes(_plaintext);
+    }
+
+    [Benchmark]
+    public int ByteArray_ProtectUnprotectRoundtrip()
+    {
+        // Traditional approach with allocations
+        var protectedData = _dataProtector.Protect(_plaintext);
+        var unprotectedData = _dataProtector.Unprotect(protectedData);
+        return protectedData.Length + unprotectedData.Length;
+    }
+
+    [Benchmark]
+    public int PooledWriter_ProtectUnprotectRoundtrip()
+    {
+        var protectBuffer = new PooledArrayBufferWriter<byte>(initialCapacity: 255);
+        var unprotectBuffer = new PooledArrayBufferWriter<byte>(initialCapacity: PlaintextLength);
+        try
+        {
+            _spanDataProtector.Protect(_plaintext, ref protectBuffer);
+            var protectedSpan = protectBuffer.WrittenSpan;
+
+            _spanDataProtector.Unprotect(protectedSpan, ref unprotectBuffer);
+            var unProtectedSpan = protectBuffer.WrittenSpan;
+
+            return protectedSpan.Length + unProtectedSpan.Length;
+        }
+        finally
+        {
+            protectBuffer.Dispose();
+            unprotectBuffer.Dispose();
+        }
+    }
+
+    [Benchmark]
+    public unsafe int RefWriter_ProtectUnprotectRoundtrip()
+    {
+        var protectBuffer = new RefPooledArrayBufferWriter<byte>(stackalloc byte[255]);
+        var unprotectBuffer = new RefPooledArrayBufferWriter<byte>(stackalloc byte[255]);
+        try
+        {
+            _spanDataProtector.Protect(_plaintext, ref protectBuffer);
+            var protectedSpan = protectBuffer.WrittenSpan;
+
+            _spanDataProtector.Unprotect(protectedSpan, ref unprotectBuffer);
+            var unProtectedSpan = unprotectBuffer.WrittenSpan;
+
+            return protectedSpan.Length + unProtectedSpan.Length;
+        }
+        finally
+        {
+            protectBuffer.Dispose();
+            unprotectBuffer.Dispose();
+        }
+    }
+}

+ 27 - 0
src/DataProtection/benchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks/Microsoft.AspNetCore.DataProtection.MicroBenchmarks.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <ServerGarbageCollection>true</ServerGarbageCollection>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <TieredCompilation>false</TieredCompilation>
+    <DefineConstants>$(DefineConstants);IS_BENCHMARKS</DefineConstants>
+    <SkipMicrobenchmarksValidation>true</SkipMicrobenchmarksValidation>
+    <AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="BenchmarkDotNet" />
+    <Reference Include="Microsoft.AspNetCore.DataProtection" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
+    <Compile Include="$(SharedSourceRoot)\Buffers\RefPooledArrayBufferWriter.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)\PooledArrayBufferWriter.cs" LinkBase="Shared" />
+  </ItemGroup>
+
+</Project>

+ 19 - 3
src/DataProtection/samples/KeyManagementSimulator/Program.cs

@@ -277,10 +277,26 @@ sealed class MockActivator(IXmlDecryptor decryptor, IAuthenticatedEncryptorDescr
 /// <summary>
 /// A mock authenticated encryptor that only applies the identity function (i.e. does nothing).
 /// </summary>
-sealed class MockAuthenticatedEncryptor : IAuthenticatedEncryptor
+sealed class MockAuthenticatedEncryptor : ISpanAuthenticatedEncryptor
 {
-    byte[] IAuthenticatedEncryptor.Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> _additionalAuthenticatedData) => ciphertext.ToArray();
-    byte[] IAuthenticatedEncryptor.Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> _additionalAuthenticatedData) => plaintext.ToArray();
+    public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> _additionalAuthenticatedData) => plaintext.ToArray();
+
+    public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : System.Buffers.IBufferWriter<byte>, allows ref struct
+    {
+        var destinationSpan = destination.GetSpan(plaintext.Length);
+        plaintext.CopyTo(destinationSpan);
+        destination.Advance(destinationSpan.Length);
+    }
+
+    public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> _additionalAuthenticatedData) => ciphertext.ToArray();
+
+    public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination)
+        where TWriter : System.Buffers.IBufferWriter<byte>, allows ref struct
+    {
+        var destinationSpan = destination.GetSpan(ciphertext.Length);
+        ciphertext.CopyTo(destinationSpan);
+        destination.Advance(destinationSpan.Length);
+    }
 }
 
 /// <summary>

+ 1 - 3
src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs

@@ -1,8 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.Collections.Generic;
 using BenchmarkDotNet.Configs;
 
 namespace BenchmarkDotNet.Attributes;
@@ -53,7 +51,7 @@ internal class AspNetCoreBenchmarkAttribute : Attribute, IConfigSource
                 throw new InvalidOperationException(message);
             }
 
-            return (IConfig)Activator.CreateInstance(configType, Array.Empty<object>());
+            return (IConfig)Activator.CreateInstance(configType, [])!;
         }
     }
 

+ 4 - 9
src/Shared/BenchmarkRunner/Program.cs

@@ -1,25 +1,20 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
 using System.Diagnostics;
-using System.IO;
 using System.Linq;
-using System.Reflection;
 using System.Text;
 using BenchmarkDotNet.Attributes;
 using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Jobs;
 using BenchmarkDotNet.Running;
-using BenchmarkDotNet.Toolchains.CsProj;
-using BenchmarkDotNet.Toolchains.DotNetCli;
 
 namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner;
 
+#nullable enable
 sealed partial class Program
 {
-    private static TextWriter _standardOutput;
-    private static StringBuilder _standardOutputText;
+    private static TextWriter? _standardOutput;
+    private static StringBuilder? _standardOutputText;
 
     static partial void BeforeMain(string[] args);
 
@@ -62,7 +57,7 @@ sealed partial class Program
 
     private static int Fail(object o, string message)
     {
-        _standardOutput?.WriteLine(_standardOutputText.ToString());
+        _standardOutput?.WriteLine(_standardOutputText?.ToString());
 
         Console.Error.WriteLine("'{0}' failed, reason: '{1}'", o, message);
         return 1;

+ 169 - 0
src/Shared/Buffers/RefPooledArrayBufferWriter.cs

@@ -0,0 +1,169 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Copied from https://github.com/dotnet/corefx/blob/b0751dcd4a419ba6731dcaa7d240a8a1946c934c/src/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers;
+
+/// <summary>
+/// A high-performance struct-based IBufferWriter&lt;byte&gt; implementation that uses ArrayPool for allocations.
+/// Designed for zero-allocation scenarios when used with generic methods via `allows ref struct` constraint.
+/// </summary>
+internal ref struct RefPooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
+{
+    private T[]? _rentedBuffer;
+    private Span<T> _buffer;
+    private int _index;
+
+    private const int MinimumBufferSize = 256;
+
+    /// <summary>
+    /// Initializes the <see cref="RefPooledArrayBufferWriter{T}"/> with initial buffer. 
+    /// </summary>
+    /// <param name="initialBuffer">The initial buffer to start writer with.</param>
+    public RefPooledArrayBufferWriter(Span<T> initialBuffer)
+    {
+        _buffer = initialBuffer;
+        _index = 0;
+    }
+
+    /// <summary>
+    /// Clears the buffer contents and returns the rented array to the ArrayPool.
+    /// This must be called to properly clean up resources.
+    /// </summary>
+    public void Dispose()
+    {
+        // to avoid `bool isDisposed` field, we can use negative index as disposed marker
+        _index = -1;
+
+        if (_rentedBuffer is not null)
+        {
+            ArrayPool<T>.Shared.Return(_rentedBuffer, clearArray: true);
+            _buffer = null;
+        }
+    }
+
+    /// <summary>
+    /// Gets a span of the written data.
+    /// </summary>
+    public readonly ReadOnlySpan<T> WrittenSpan
+    {
+        get
+        {
+            Debug.Assert(_index >= 0);
+            return _buffer.Slice(0, _index);
+        }
+    }
+
+    /// <summary>
+    /// Gets a memory segment representing the available space for writing.
+    /// </summary>
+    /// <param name="sizeHint">A hint about the minimum size needed. Ignored in this implementation as resizing is not performed.</param>
+    /// <returns>A Memory&lt;byte&gt; segment for the available space.</returns>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Memory<T> GetMemory(int sizeHint = 0)
+    {
+#if NET
+        throw new UnreachableException("RefPooledArrayBufferWriter does not support GetMemory");
+#else
+        throw new NotSupportedException("RefPooledArrayBufferWriter does not support GetMemory");
+#endif
+    }
+
+    /// <summary>
+    /// Gets a span representing the available space for writing.
+    /// </summary>
+    /// <param name="sizeHint">A hint about the minimum size needed. Ignored in this implementation as resizing is not performed.</param>
+    /// <returns>A Span&lt;byte&gt; for the available space.</returns>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Span<T> GetSpan(int sizeHint = 0)
+    {
+        Debug.Assert(_index >= 0);
+        CheckAndResizeBuffer(sizeHint);
+
+        return _buffer.Slice(_index);
+    }
+
+    /// <summary>
+    /// Advances the write position by the specified count.
+    /// </summary>
+    /// <param name="count">The number of bytes written.</param>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if count is negative.</exception>
+    /// <exception cref="InvalidOperationException">Thrown if advancing would exceed buffer capacity.</exception>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Advance(int count)
+    {
+        Debug.Assert(_index >= 0);
+        if (count < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be negative.");
+        }
+
+        if (_index + count > _buffer.Length)
+        {
+            throw new InvalidOperationException($"Cannot advance past the end of the buffer. Current position: {_index}, Capacity: {_buffer.Length}, Requested advance: {count}.");
+        }
+
+        _index += count;
+    }
+
+    private void CheckAndResizeBuffer(int sizeHint)
+    {
+        if (sizeHint < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(sizeHint), actualValue: sizeHint, $"{nameof(sizeHint)} ('{sizeHint}') must be a non-negative value.");
+        }
+        if (sizeHint == 0)
+        {
+            sizeHint = MinimumBufferSize;
+        }
+
+        // initial buffer is still in use
+        if (_rentedBuffer is null)
+        {
+            var bufferSpace = _buffer.Length - _index;
+            if (bufferSpace < sizeHint)
+            {
+                // initial buffer is not enough, we need to start renting from the pool
+                var rentedInitialSize = _buffer.Length + Math.Max(sizeHint, _buffer.Length);
+                _rentedBuffer = ArrayPool<T>.Shared.Rent(rentedInitialSize);
+
+                _buffer.CopyTo(_rentedBuffer);
+                _buffer = _rentedBuffer;
+
+                Debug.Assert(_rentedBuffer.Length - _index > 0);
+                Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
+            }
+
+            return;
+        }
+
+        var availableSpace = _buffer.Length - _index;
+        if (sizeHint <= availableSpace)
+        {
+            return;
+        }
+
+        // we are using rented buffer, so grow it if needed
+        var growBy = Math.Max(sizeHint, _buffer.Length);
+        var newSize = checked(_buffer.Length + growBy);
+
+        var oldBuffer = _rentedBuffer;
+        _rentedBuffer = ArrayPool<T>.Shared.Rent(newSize);
+
+        Debug.Assert(oldBuffer.Length >= _index);
+        Debug.Assert(_rentedBuffer.Length >= _index);
+
+        var previousBuffer = oldBuffer.AsSpan(0, _index);
+        previousBuffer.CopyTo(_rentedBuffer);
+        ArrayPool<T>.Shared.Return(oldBuffer);
+
+        _buffer = _rentedBuffer;
+
+        Debug.Assert(_rentedBuffer.Length - _index > 0);
+        Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
+    }
+}

+ 37 - 6
src/Shared/PooledArrayBufferWriter.cs

@@ -22,18 +22,32 @@ internal sealed class PooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
 
     public PooledArrayBufferWriter(int initialCapacity)
     {
-        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCapacity);
+        if (initialCapacity <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(initialCapacity), actualValue: initialCapacity, $"{nameof(initialCapacity)} ('{initialCapacity}') must be a non-negative and non-zero value.");
+        }
 
         _rentedBuffer = ArrayPool<T>.Shared.Rent(initialCapacity);
         _index = 0;
     }
 
-    public ReadOnlyMemory<T> WrittenMemory
+    /// <summary>
+    /// Gets a span of the written data.
+    /// </summary>
+    public ReadOnlySpan<T> WrittenSpan
     {
         get
         {
             CheckIfDisposed();
+            return _rentedBuffer.AsSpan(0, _index);
+        }
+    }
 
+    public ReadOnlyMemory<T> WrittenMemory
+    {
+        get
+        {
+            CheckIfDisposed();
             return _rentedBuffer.AsMemory(0, _index);
         }
     }
@@ -43,7 +57,6 @@ internal sealed class PooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
         get
         {
             CheckIfDisposed();
-
             return _index;
         }
     }
@@ -106,14 +119,29 @@ internal sealed class PooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
 
     private static void ThrowObjectDisposedException()
     {
+#if NET
         throw new ObjectDisposedException(nameof(ArrayBufferWriter<T>));
+#else
+        throw new ObjectDisposedException(nameof(IBufferWriter<T>));
+#endif
     }
 
+    public void Advance(uint count)
+        => Advance((int)count);
+
     public void Advance(int count)
     {
-        CheckIfDisposed();
+        if (count == 0)
+        {
+            // no-op
+            return;
+        }
 
-        ArgumentOutOfRangeException.ThrowIfNegative(count);
+        CheckIfDisposed();
+        if (count < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(count), actualValue: count, $"{nameof(count)} ('{count}') must be a non-negative value.");
+        }
 
         if (_index > _rentedBuffer.Length - count)
         {
@@ -143,7 +171,10 @@ internal sealed class PooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
     {
         Debug.Assert(_rentedBuffer != null);
 
-        ArgumentOutOfRangeException.ThrowIfNegative(sizeHint);
+        if (sizeHint < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(sizeHint), actualValue: sizeHint, $"{nameof(sizeHint)} ('{sizeHint}') must be a non-negative value.");
+        }
 
         if (sizeHint == 0)
         {

+ 1 - 1
src/Shared/Shared.slnf

@@ -10,7 +10,7 @@
       "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
       "src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
       "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
-       "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj",
+      "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj",
       "src\\Shared\\BrowserTesting\\src\\Microsoft.AspNetCore.BrowserTesting.csproj",
       "src\\Shared\\test\\Shared.Tests\\Microsoft.AspNetCore.Shared.Tests.csproj",
       "src\\Testing\\src\\Microsoft.AspNetCore.InternalTesting.csproj"

+ 196 - 0
src/Shared/test/Shared.Tests/Buffers/PooledArrayBufferWriterTests.cs

@@ -0,0 +1,196 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+
+namespace Microsoft.AspNetCore.Shared.Tests.Buffers;
+
+public class PooledArrayBufferWriterTests
+{
+    [Theory]
+    [InlineData(1)]
+    [InlineData(256)]
+    [InlineData(1024)]
+    public void Constructor_WithInitialCapacity_InitializesWithRequestedCapacity(int initialCapacity)
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(initialCapacity);
+
+        Assert.Equal(0, writer.WrittenCount);
+        Assert.True(writer.Capacity >= initialCapacity && writer.Capacity > 0);
+    }
+
+    [Theory]
+    [InlineData(0)]
+    [InlineData(-1)]
+    [InlineData(-100)]
+    public void Constructor_WithInvalidInitialCapacity_Throws(int invalidCapacity)
+    {
+        Assert.Throws<ArgumentOutOfRangeException>(() => new PooledArrayBufferWriter<byte>(invalidCapacity));
+    }
+
+    [Theory]
+    [InlineData(-1)]
+    [InlineData(-100)]
+    public void Advance_WithNegativeCount_Throws(int negativeCount)
+    {
+        using var writer = new PooledArrayBufferWriter<byte>();
+        Assert.Throws<ArgumentOutOfRangeException>(() => writer.Advance(negativeCount));
+    }
+
+    [Fact]
+    public void Advance_BeyondCapacity_Throws()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(10);
+        Assert.Throws<InvalidOperationException>(() => writer.Advance(20));
+    }
+
+    [Fact]
+    public void GetSpan_WithInvalidSizeHint_Throws()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>();
+        Assert.Throws<ArgumentOutOfRangeException>(() => writer.GetSpan(-1));
+    }
+
+    [Fact]
+    public void GetMemory_WithInvalidSizeHint_Throws()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>();
+        Assert.Throws<ArgumentOutOfRangeException>(() => writer.GetMemory(-1));
+    }
+
+    [Fact]
+    public void WrittenCount_ReturnsNumberOfBytesWritten_AsPerAdvanceCalls()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>();
+        writer.Advance(2);
+        Assert.Equal(2, writer.WrittenCount);
+        writer.Advance(1);
+        Assert.Equal(3, writer.WrittenCount);
+        writer.Advance(5);
+        Assert.Equal(8, writer.WrittenCount);
+    }
+
+    [Theory]
+    [InlineData(0)]
+    [InlineData(10)]
+    [InlineData(100)]
+    [InlineData(1000)]
+    public void GetSpan_WithSizeHint_ResizesBufferIfNeeded(int sizeHint)
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(10);
+        var span = writer.GetSpan(sizeHint);
+        Assert.True(span.Length >= sizeHint && span.Length > 0);
+    }
+
+    [Theory]
+    [InlineData(0)]
+    [InlineData(10)]
+    [InlineData(100)]
+    [InlineData(1000)]
+    public void GetMemory_WithSizeHint_ResizesBufferIfNeeded(int sizeHint)
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(10);
+        var memory = writer.GetMemory(sizeHint);
+        Assert.True(memory.Length >= sizeHint && memory.Length > 0);
+    }
+
+    [Fact]
+    public void Advance_WithZero_HasNoEffect()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(256);
+        writer.Advance(10);
+        var countBefore = writer.WrittenCount;
+        writer.Advance(0);
+
+        Assert.Equal(countBefore, writer.WrittenCount);
+    }
+
+    [Fact]
+    public void WrittenSpan_ReturnsSpanOfWrittenData()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(256);
+        var span = writer.GetSpan();
+        span[0] = 42;
+        span[1] = 99;
+        writer.Advance(2);
+
+        var writtenSpan = writer.WrittenSpan;
+        Assert.Equal(2, writtenSpan.Length);
+        Assert.Equal(42, writtenSpan[0]);
+        Assert.Equal(99, writtenSpan[1]);
+    }
+
+    [Fact]
+    public void WrittenMemory_ReturnsMemoryOfWrittenData()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(256);
+        var span = writer.GetSpan();
+        span[0] = 42;
+        span[1] = 99;
+        writer.Advance(2);
+
+        var writtenMemory = writer.WrittenMemory;
+        Assert.Equal(2, writtenMemory.Length);
+        Assert.Equal(42, writtenMemory.Span[0]);
+        Assert.Equal(99, writtenMemory.Span[1]);
+    }
+
+    [Fact]
+    public void Clear_ResetsWrittenCountAndClearsData()
+    {
+        using var writer = new PooledArrayBufferWriter<byte>(256);
+        var span = writer.GetSpan();
+        span[0] = 42;
+        writer.Advance(1);
+        writer.Clear();
+
+        Assert.Equal(0, writer.WrittenCount);
+        Assert.Equal(0, writer.WrittenSpan.Length);
+    }
+
+    [Fact]
+    public void Clear_AfterDispose_Throws()
+    {
+        var writer = new PooledArrayBufferWriter<byte>();
+        writer.Dispose();
+
+        Assert.Throws<ObjectDisposedException>(() => writer.Clear());
+        Assert.Throws<ObjectDisposedException>(() => writer.GetSpan());
+        Assert.Throws<ObjectDisposedException>(() => writer.GetMemory());
+        Assert.Throws<ObjectDisposedException>(() => writer.Advance(1));
+        Assert.Throws<ObjectDisposedException>(() => _ = writer.WrittenSpan);
+        Assert.Throws<ObjectDisposedException>(() => _ = writer.WrittenMemory);
+        Assert.Throws<ObjectDisposedException>(() => _ = writer.WrittenCount);
+        Assert.Throws<ObjectDisposedException>(() => _ = writer.Capacity);
+        Assert.Throws<ObjectDisposedException>(() => _ = writer.FreeCapacity);
+    }
+
+    [Fact]
+    public void BufferPreservesContentOnGrowth_AndAllowsWritingMore()
+    {
+        var initialCapacity = 10;
+        using var writer = new PooledArrayBufferWriter<byte>(initialCapacity);
+
+        var span = writer.GetSpan();
+        span[0] = 42;
+        span[1] = 99;
+        writer.Advance(2);
+
+        var contentBefore = writer.WrittenSpan.ToArray();
+
+        // Force buffer growth
+        span = writer.GetSpan(100);
+        Assert.True(writer.Capacity > initialCapacity);
+
+        var contentAfter = writer.WrittenSpan.ToArray();
+        Assert.Equal(contentBefore, contentAfter);
+
+        // we should be able to fill in at least 100-(10-2) bytes,
+        // because we requested 100 span on 8 bytes free buffer:
+        for (var i = 0; i < 92; i++)
+        {
+            span[i] = 42;
+        }
+    }
+}
+

+ 310 - 0
src/Shared/test/Shared.Tests/Buffers/RefPooledArrayBufferWriterTests.cs

@@ -0,0 +1,310 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace Microsoft.AspNetCore.Shared.Tests.Buffers;
+
+public class RefPooledArrayBufferWriterTests
+{
+    [Theory]
+    [InlineData(16)]
+    [InlineData(256)]
+    [InlineData(1024)]
+    public void Constructor_WithInitialBuffer_InitializesCorrectly(int initialCapacity)
+    {
+        Span<byte> initialBuffer = stackalloc byte[initialCapacity];
+        var writer = new RefPooledArrayBufferWriter<byte>(initialBuffer);
+
+        var span = writer.GetSpan();
+        Assert.True(span.Length >= initialCapacity);
+
+        writer.Dispose();
+    }
+
+    [Theory]
+    [InlineData(-1)]
+    [InlineData(-100)]
+    public void Advance_WithNegativeCount_Throws(int negativeCount)
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        // ref struct cannot be captured in lambda, so using try-catch
+        try
+        {
+            writer.Advance(negativeCount);
+            Assert.Fail("Expected ArgumentOutOfRangeException");
+        }
+        catch (ArgumentOutOfRangeException)
+        {
+            // Expected
+        }
+        finally
+        {
+            writer.Dispose();
+        }
+    }
+
+    [Fact]
+    public void Advance_BeyondCapacity_Throws()
+    {
+        Span<byte> buffer = stackalloc byte[10];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        // ref struct cannot be captured in lambda, use try-catch
+        try
+        {
+            writer.Advance(20);
+            Assert.Fail("Expected InvalidOperationException");
+        }
+        catch (InvalidOperationException)
+        {
+            // Expected
+        }
+        finally
+        {
+            writer.Dispose();
+        }
+    }
+
+    [Fact]
+    public void GetSpan_WithInvalidSizeHint_Throws()
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        // ref struct cannot be captured in lambda, use try-catch
+        try
+        {
+            writer.GetSpan(-1);
+            Assert.Fail("Expected ArgumentOutOfRangeException");
+        }
+        catch (ArgumentOutOfRangeException)
+        {
+            // Expected
+        }
+        finally
+        {
+            writer.Dispose();
+        }
+    }
+
+    [Fact]
+    public void GetMemory_ThrowsNotSupported()
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        // ref struct cannot be captured in lambda, use try-catch
+        try
+        {
+            writer.GetMemory();
+            Assert.Fail("Expected NotSupportedException or UnreachableException");
+        }
+#if NET
+        catch (UnreachableException)
+        {
+            // Expected (for .NET Core)
+        }
+#else
+        catch (NotSupportedException)
+        {
+            // Expected (for .NET Framework and older versions)
+        }
+#endif
+        finally
+        {
+            writer.Dispose();
+        }
+    }
+
+    [Theory]
+    [InlineData(0)]
+    [InlineData(10)]
+    [InlineData(100)]
+    [InlineData(1000)]
+    public void GetSpan_WithSizeHint_ResizesBufferIfNeeded(int sizeHint)
+    {
+        Span<byte> buffer = stackalloc byte[10];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        var span = writer.GetSpan(sizeHint);
+        Assert.True(span.Length >= sizeHint && span.Length > 0);
+
+        writer.Dispose();
+    }
+
+    [Fact]
+    public void WrittenSpan_ReturnsSpanOfWrittenData()
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        var span = writer.GetSpan();
+        span[0] = 42;
+        span[1] = 99;
+        writer.Advance(2);
+
+        var writtenSpan = writer.WrittenSpan;
+        Assert.Equal(2, writtenSpan.Length);
+        Assert.Equal(42, writtenSpan[0]);
+        Assert.Equal(99, writtenSpan[1]);
+
+        writer.Dispose();
+    }
+
+    [Fact]
+    public void BufferPreservesContentOnGrowth_FromStackToPooledBuffer()
+    {
+        var initialCapacity = 10;
+        Span<byte> initialBuffer = stackalloc byte[initialCapacity];
+        var writer = new RefPooledArrayBufferWriter<byte>(initialBuffer);
+
+        var span = writer.GetSpan();
+        span[0] = 42;
+        span[1] = 99;
+        writer.Advance(2);
+
+        var contentBefore = writer.WrittenSpan.ToArray();
+
+        // Force buffer growth from stack to pooled array
+        span = writer.GetSpan(100);
+
+        var contentAfter = writer.WrittenSpan.ToArray();
+        Assert.Equal(contentBefore, contentAfter);
+
+        // Verify we can write at least 92 more bytes (100 - 8 bytes remaining)
+        for (var i = 0; i < 92; i++)
+        {
+            span[i] = (byte)(i % 256);
+        }
+        writer.Advance(92);
+
+        Assert.Equal(94, writer.WrittenSpan.Length);
+
+        writer.Dispose();
+    }
+
+    [Fact]
+    public void Buffer_AdvancesOnBoundaryLimits()
+    {
+        Span<byte> initialBuffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(initialBuffer);
+
+        _ = writer.GetSpan();
+        writer.Advance(256); // should not throw
+        Assert.Equal(256, writer.WrittenSpan.Length);
+    }
+
+    [Fact]
+    public void BufferPreservesContentOnGrowth_PooledToLargerPooledBuffer()
+    {
+        Span<byte> initialBuffer = stackalloc byte[16];
+        var writer = new RefPooledArrayBufferWriter<byte>(initialBuffer);
+
+        // Force initial growth to pooled buffer
+        var span = writer.GetSpan(100);
+        for (var i = 0; i < 50; i++)
+        {
+            span[i] = (byte)(i % 256);
+        }
+        writer.Advance(50);
+
+        var contentBefore = writer.WrittenSpan.ToArray();
+
+        // Force second growth from pooled to larger pooled
+        span = writer.GetSpan(500);
+
+        var contentAfter = writer.WrittenSpan.ToArray();
+        Assert.Equal(contentBefore, contentAfter);
+
+        // Verify content integrity
+        for (var i = 0; i < 50; i++)
+        {
+            Assert.Equal((byte)(i % 256), contentAfter[i]);
+        }
+
+        writer.Dispose();
+    }
+
+    [Fact]
+    public void MultipleWrites_WorkCorrectly()
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        // First write
+        var span = writer.GetSpan();
+        span[0] = 1;
+        span[1] = 2;
+        writer.Advance(2);
+
+        // Second write
+        span = writer.GetSpan();
+        span[0] = 3;
+        span[1] = 4;
+        writer.Advance(2);
+
+        // Third write
+        span = writer.GetSpan();
+        span[0] = 5;
+        writer.Advance(1);
+
+        var written = writer.WrittenSpan;
+        Assert.Equal(5, written.Length);
+        Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, written.ToArray());
+
+        writer.Dispose();
+    }
+
+    [Fact]
+    public void GetSpan_ReturnsConsistentReference_BeforeAdvance()
+    {
+        Span<byte> buffer = stackalloc byte[256];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        var span1 = writer.GetSpan();
+        span1[0] = 42;
+
+        var span2 = writer.GetSpan();
+        Assert.Equal(42, span2[0]); // Should see the same buffer
+
+        writer.Advance(1);
+        Assert.Equal(42, writer.WrittenSpan[0]);
+
+        writer.Dispose();
+    }
+
+    [Theory]
+    [InlineData(1)]
+    [InlineData(10)]
+    [InlineData(100)]
+    public void LargeWrite_AcrossGrowthBoundary_PreservesData(int iterations)
+    {
+        Span<byte> buffer = stackalloc byte[8];
+        var writer = new RefPooledArrayBufferWriter<byte>(buffer);
+
+        for (int i = 0; i < iterations; i++)
+        {
+            var span = writer.GetSpan(10);
+            for (int j = 0; j < 10; j++)
+            {
+                span[j] = (byte)((i * 10 + j) % 256);
+            }
+            writer.Advance(10);
+        }
+
+        var written = writer.WrittenSpan;
+        Assert.Equal(iterations * 10, written.Length);
+
+        // Verify all data
+        for (int i = 0; i < iterations * 10; i++)
+        {
+            Assert.Equal((byte)(i % 256), written[i]);
+        }
+
+        writer.Dispose();
+    }
+}

+ 2 - 0
src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj

@@ -43,6 +43,8 @@
     <Compile Include="$(SharedSourceRoot)Debugger\DictionaryItemDebugView.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)Debugger\DictionaryDebugView.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)Debugger\StringValuesDictionaryDebugView.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)PooledArrayBufferWriter.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)Buffers\RefPooledArrayBufferWriter.cs" LinkBase="Shared" />
   </ItemGroup>
 
   <ItemGroup>