Skip to content

feat: "Spanify" DataProtector #62903

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b1a4b40
implement encrypt size calculation
DeagleGross Jul 15, 2025
aecf589
implement geteencyrptedsize
DeagleGross Jul 16, 2025
198501e
try encrypt
DeagleGross Jul 16, 2025
a4e3ca4
simplify net10 impl
DeagleGross Jul 16, 2025
8d49973
simplify tests and cbc
DeagleGross Jul 16, 2025
ae6fb76
cnggcm
DeagleGross Jul 16, 2025
9caa72e
aes
DeagleGross Jul 16, 2025
b9b45b2
something
DeagleGross Jul 16, 2025
b3e5b42
key ring based part
DeagleGross Jul 16, 2025
4eb96e3
Merge branch 'main' into dmkorolev/dataprotector-spans
DeagleGross Jul 23, 2025
afd344c
refactor
DeagleGross Jul 24, 2025
fd7f929
finally passed!
DeagleGross Jul 24, 2025
afbad47
generate test for plain text
DeagleGross Jul 24, 2025
34d52e7
correct empty plain text scenario
DeagleGross Jul 24, 2025
18eec02
minor improvements
DeagleGross Jul 24, 2025
9e4a634
hide in internal + docs
DeagleGross Jul 24, 2025
8829566
to public api - its a nightmare to implement otherwise
DeagleGross Jul 24, 2025
5f240c2
fix build
DeagleGross Jul 24, 2025
8d275ac
wip
DeagleGross Jul 24, 2025
8f5b762
re-review
DeagleGross Jul 24, 2025
efd4f79
implement timelimitedDataProtector
DeagleGross Jul 24, 2025
6142309
introduce `IOptimizedDataProtector`
DeagleGross Jul 25, 2025
ae5bb82
introduce optimized IAuthenticatedEncryptor
DeagleGross Jul 25, 2025
0646d1a
fix dataprotector usage
DeagleGross Jul 25, 2025
3d37955
fix build?
DeagleGross Jul 25, 2025
bbfce53
move to a separate ISpanAuthenticatedEncryptor
DeagleGross Jul 25, 2025
b0fc525
refactor to ISpan interfaces
DeagleGross Jul 28, 2025
d4b1fc9
correct span allocation
DeagleGross Jul 28, 2025
146af98
use TryEncrypt from Encrypt()
DeagleGross Jul 28, 2025
630e451
fix
DeagleGross Jul 28, 2025
983b69f
Merge branch 'main' into dmkorolev/dataprotector-spans
DeagleGross Jul 31, 2025
948052c
init
DeagleGross Aug 1, 2025
37a3277
inheritance!
DeagleGross Aug 2, 2025
0e1dfd6
separate impl
DeagleGross Aug 2, 2025
35f7522
distinguish impl
DeagleGross Aug 2, 2025
05fdcc3
other impls
DeagleGross Aug 3, 2025
f223b45
tests & api
DeagleGross Aug 3, 2025
c029655
init decrypt
DeagleGross Aug 3, 2025
fe478d2
cnggcm
DeagleGross Aug 4, 2025
72aecba
init cbc
DeagleGross Aug 4, 2025
29e7622
cbc
DeagleGross Aug 4, 2025
516729c
mockable
DeagleGross Aug 4, 2025
21b7d87
AesGcm
DeagleGross Aug 4, 2025
5a1ea5c
impl managed
DeagleGross Aug 4, 2025
f561796
fix slices everywhere + rollback managedauth to a proper impl
DeagleGross Aug 4, 2025
c1b203f
ispanauth: decryption ready
DeagleGross Aug 4, 2025
614b569
intro ispandataprotector.unprotect \ fix warnings \ dont change timel…
DeagleGross Aug 5, 2025
9f24867
span data protector unprotect
DeagleGross Aug 6, 2025
27f7609
"final" review
DeagleGross Aug 6, 2025
e3726ec
avoid blank lines!
DeagleGross Aug 6, 2025
bc44688
push project for microbenchmarks
DeagleGross Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AspNetCore.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,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" Id="71744108-dc12-4558-9788-ed62c9f0441c" />
</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" />
Expand Down Expand Up @@ -1143,6 +1146,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" />
Expand Down Expand Up @@ -1190,8 +1194,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" />
Expand Down
50 changes: 50 additions & 0 deletions src/DataProtection/Abstractions/src/ISpanDataProtector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.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>
/// Determines the size of the protected data in order to then use <see cref="TryProtect(ReadOnlySpan{byte}, Span{byte}, out int)"/>."/>.
/// </summary>
/// <param name="plainTextLength">The plain text length which will be encrypted later.</param>
/// <returns>The size of the protected data.</returns>
int GetProtectedSize(int plainTextLength);

/// <summary>
/// Returns the size of the decrypted data for a given ciphertext length.
/// Size can be an over-estimation, the specific size will be written in <i>bytesWritten</i> parameter of the <see cref="TryUnprotect"/>
/// </summary>
/// <param name="cipherTextLength">Length of the cipher text that will be decrypted later.</param>
/// <returns>The length of the decrypted data.</returns>
int GetUnprotectedSize(int cipherTextLength);

/// <summary>
/// Attempts to encrypt and tamper-proof a piece of data.
/// </summary>
/// <param name="plainText">The input to encrypt.</param>
/// <param name="destination">The ciphertext blob, including authentication tag.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
/// <returns>true if destination is long enough to receive the encrypted data; otherwise, false.</returns>
bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten);

/// <summary>
/// Attempts to validate the authentication tag of and decrypt a blob of encrypted data.
/// </summary>
/// <param name="cipherText">The encrypted data to decrypt.</param>
/// <param name="destination">The decrypted output.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
/// <returns>true if decryption was successful; otherwise, false.</returns>
bool TryUnprotect(ReadOnlySpan<byte> cipherText, Span<byte> destination, out int bytesWritten);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Microsoft.AspNetCore.DataProtection.IDataProtector</Description>
<Compile Include="$(SharedSourceRoot)CallerArgument\CallerArgumentExpressionAttribute.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != '$(DefaultNetCoreTargetFramework)'">
<Reference Include="System.Memory" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.DataProtection.Abstractions.Tests" />
</ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/DataProtection/Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
#nullable enable
Microsoft.AspNetCore.DataProtection.ISpanDataProtector
Microsoft.AspNetCore.DataProtection.ISpanDataProtector.GetProtectedSize(int plainTextLength) -> int
Microsoft.AspNetCore.DataProtection.ISpanDataProtector.GetUnprotectedSize(int cipherTextLength) -> int
Microsoft.AspNetCore.DataProtection.ISpanDataProtector.TryProtect(System.ReadOnlySpan<byte> plainText, System.Span<byte> destination, out int bytesWritten) -> bool
Microsoft.AspNetCore.DataProtection.ISpanDataProtector.TryUnprotect(System.ReadOnlySpan<byte> cipherText, System.Span<byte> destination, out int bytesWritten) -> bool
1 change: 1 addition & 0 deletions src/DataProtection/DataProtection.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.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>
/// Returns the size of the encrypted data for a given plaintext length.
/// </summary>
/// <param name="plainTextLength">Length of the plain text that will be encrypted later</param>
/// <returns>The length of the encrypted data</returns>
int GetEncryptedSize(int plainTextLength);

/// <summary>
/// Returns the size of the decrypted data for a given ciphertext length.
/// Size can be an over-estimation, the specific size will be written in <i>bytesWritten</i> parameter of the <see cref="TryDecrypt"/>
/// </summary>
/// <param name="cipherTextLength">Length of the cipher text that will be decrypted later.</param>
/// <returns>The length of the decrypted data.</returns>
int GetDecryptedSize(int cipherTextLength);

/// <summary>
/// Attempts to encrypt and tamper-proof a piece of data.
/// </summary>
/// <param name="plaintext">The input to encrypt.</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 decryption call.
/// </param>
/// <param name="destination">The ciphertext blob, including authentication tag.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
/// <returns>true if destination is long enough to receive the encrypted data; otherwise, false.</returns>
bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);

/// <summary>
/// Attempts to validate the authentication tag of and decrypt a blob of encrypted data.
/// </summary>
/// <param name="cipherText">The encrypted data to decrypt.</param>
/// <param name="additionalAuthenticatedData">
/// A piece of data which was included in the authentication tag during encryption.
/// This input may be zero bytes in length. The same AAD must be specified in the corresponding encryption call.
/// </param>
/// <param name="destination">The decrypted output.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written into destination</param>
/// <returns>true if decryption was successful; otherwise, false.</returns>
bool TryDecrypt(ReadOnlySpan<byte> cipherText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);
}
Loading
Loading