diff --git a/Directory.Packages.props b/Directory.Packages.props
index e249a871f..08bd2efc9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -44,6 +44,7 @@
+
diff --git a/all.sln b/all.sln
index 9a163b1d9..425339f89 100644
--- a/all.sln
+++ b/all.sln
@@ -155,6 +155,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JobsSample", "examples\Jobs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Test", "test\Dapr.Workflow.Test\Dapr.Workflow.Test.csproj", "{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography", "src\Dapr.Cryptography\Dapr.Cryptography.csproj", "{7628358D-FE3B-42F2-BE10-D0A6F7C03242}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography.Test", "test\Dapr.Cryptography.Test\Dapr.Cryptography.Test.csproj", "{0637289C-D284-417C-8032-182E31C5ACA5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cryptography", "Cryptography", "{0F81BB97-07B1-4186-992C-31EB42D06383}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CryptographySample", "examples\Cryptography\CryptographySample\CryptographySample.csproj", "{640E587A-43C6-4ADC-BC60-374474AFDA64}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -403,6 +411,18 @@ Global
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7628358D-FE3B-42F2-BE10-D0A6F7C03242}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7628358D-FE3B-42F2-BE10-D0A6F7C03242}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7628358D-FE3B-42F2-BE10-D0A6F7C03242}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7628358D-FE3B-42F2-BE10-D0A6F7C03242}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0637289C-D284-417C-8032-182E31C5ACA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0637289C-D284-417C-8032-182E31C5ACA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0637289C-D284-417C-8032-182E31C5ACA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0637289C-D284-417C-8032-182E31C5ACA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {640E587A-43C6-4ADC-BC60-374474AFDA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {640E587A-43C6-4ADC-BC60-374474AFDA64}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {640E587A-43C6-4ADC-BC60-374474AFDA64}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {640E587A-43C6-4ADC-BC60-374474AFDA64}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -477,6 +497,10 @@ Global
{D9697361-232F-465D-A136-4561E0E88488} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673} = {D9697361-232F-465D-A136-4561E0E88488}
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {7628358D-FE3B-42F2-BE10-D0A6F7C03242} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {0637289C-D284-417C-8032-182E31C5ACA5} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {0F81BB97-07B1-4186-992C-31EB42D06383} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
+ {640E587A-43C6-4ADC-BC60-374474AFDA64} = {0F81BB97-07B1-4186-992C-31EB42D06383}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/_index.md
new file mode 100644
index 000000000..82bb9e214
--- /dev/null
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/_index.md
@@ -0,0 +1,13 @@
+---
+type: docs
+title: "Dapr Jobs .NET SDK"
+linkTitle: "Jobs"
+weight: 70000
+description: Get up and running with Dapr Jobs and the Dapr .NET SDK
+---
+
+With the Dapr Job package, you can interact with the Dapr Cryptography APIs from a .NET application to trigger future operations
+to run according to a predefined schedule with an optional payload.
+
+To get started, walk through the [Dapr Cryptography]({{< ref dotnet-cryptography-howto.md >}}) how-to guide and refer to
+[best practices documentation]({{< ref dotnet-cryptographyclient-usage.md >}}) for additional guidance.
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/dotnet-cryptography-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/dotnet-cryptography-howto.md
new file mode 100644
index 000000000..92793ba18
--- /dev/null
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/dotnet-cryptography-howto.md
@@ -0,0 +1,208 @@
+---
+type: docs
+title: "How to: Author and manage Dapr Cryptography operations in the .NET SDK"
+linkTitle: "How to: Author & manage cryptography operations"
+weight: 71000
+description: Learn how to author and manage Dapr Cryptography operations using the .NET SDK
+---
+
+Let's encrypt some data and subsequently decrypt this information using the Cryptography capabilities of
+the Dapr .NET SDK. We'll use the [simple example provided here](), for the following demonstration and walk through
+it as an explainer of how you can encrypt and decrypt arbitrary byte arrays or streams of data. In this guide, you will:
+
+- Deploy a .NET Web API application ([CryptographSample]())
+- Utilize the Dapr .NET Cryptography SDK to encrypt and decrypt a payload
+
+In the .NET example project:
+- The main [`Program.cs`]() file comprises the entirety of this demonstration.
+
+## Prerequisites
+- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
+- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
+- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
+- [Dapr.Cryptography](https://www.nuget.org/packages/Dapr.Cryptography) NuGet package installed to your project
+
+## Set up the environment
+Clone the [.NET SDK repo](https://github.com/dapr/dotnet-sdk).
+
+```sh
+git clone https://github.com/dapr/dotnet-sdk.git
+```
+
+From the .NET SDK root directory, navigate to the Dapr Cryptography example.
+
+```sh
+cd examples/Cryptography
+```
+
+## Run the application locally
+
+To run the Dapr application, you need to start the .NET program and a Dapr sidecar. Navigate to the `CryptographySample` directory.
+
+```sh
+cd CryptographySample
+```
+
+We'll run a command that starts both the Dapr sidecar and the .NET program at the same time.
+
+```sh
+dapr run --app-id cryptoapp --dapr-grpc-port 4001 --dapr-http-port 3500 -- dotnet run
+```
+
+> Dapr listens for HTTP requests at `http://localhost:3500` and internal Jobs gRPC requests at `http://localhost:4001`.
+
+## Register the Dapr Encryption client with dependency injection
+The Dapr Cryptography SDK provides an extension method to simplify the registration of the Dapr encryption client.
+Before completing the dependency injection registration in `Program.cs`, add the following line:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+
+//Add anywhere between these two lines
+builder.Services.AddDaprEncryptionClient();
+
+var app = builder.Build();
+```
+
+It's possible that you may want to provide some configuration options to the Dapr encryption client that
+should be present with each call to the sidecar such as a Dapr API token, or you want to use a non-standard
+HTTP or gRPC endpoint. This is possible through use of an overload of the registration method that allows
+configuration of a `DaprEncryptionClientBuilder` instance:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddDaprEncryptionClient((_, daprEncryptionClientBuilder) =>
+{
+ daprEncryptionClientBuilder.UseDaprApiToken("abc123");
+ daprEncryptionClientBuilder.UseHttpEndpoint("http://localhost:8512"); //Non-standard sidecar HTTP endpoint
+});
+
+var app = builder.Build();
+```
+
+Still, it's possible that whatever values you wish to inject need to be retrieved from some other source, itself
+registered as a dependency. There's one more overload you can use to inject an `IServiceProvider` into the
+configuration action method. In the following example, we register a fictional singleton that can retrieve
+secrets from somewhere and pass it into the configuration method for `AddDaprEncryptionClient` so
+we can retrieve our Dapr API token from somewhere else for registration here:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddDaprJobsClient((serviceProvider, daprEncryptionClientBuilder) =>
+{
+ var secretRetriever = serviceProvider.GetRequiredService();
+ var daprApiToken = secretRetriever.GetSecret("DaprApiToken").Value;
+ daprJobsClientBuilder.UseDaprApiToken(daprApiToken);
+
+ daprJobsClientBuilder.UseHttpEndpoint("http://localhost:8512");
+});
+
+var app = builder.Build();
+```
+
+## Use the Dapr Encryption client using IConfiguration
+It's possible to configure the Dapr Encryption client using the values in your registered `IConfiguration` as well without
+explicitly specifying each of the value overrides using the `DaprEncryptionlientBuilder` as demonstrated in the previous
+section. Rather, by populating an `IConfiguration` made available through dependency injection the `AddDaprEncryptionClient()`
+registration will automatically use these values over their respective defaults.
+
+Start by populating the values in your configuration. This can be done in several different ways as demonstrated below.
+
+### Configuration via `ConfigurationBuilder`
+Application settings can be configured without using a configuration source and by instead populating the value in-memory
+using a `ConfigurationBuilder` instance:
+
+```csharp
+var builder = WebApplication.CreateBuilder();
+
+//Create the configuration
+var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary {
+ { "DAPR_HTTP_ENDPOINT", "http://localhost:54321" },
+ { "DAPR_API_TOKEN", "abc123" }
+ })
+ .Build();
+
+builder.Configuration.AddConfiguration(configuration);
+builder.Services.AddDaprEncryptionClient(); //This will automatically populate the HTTP endpoint and API token values from the IConfiguration
+```
+
+### Configuration via Environment Variables
+Application settings can be accessed from environment variables available to your application.
+
+The following environment variables will be used to populate both the HTTP endpoint and API token used to register the
+Dapr Jobs client.
+
+| Key | Value |
+| --- | --- |
+| DAPR_HTTP_ENDPOINT | http://localhost:54321 |
+| DAPR_API_TOKEN | abc123 |
+
+```csharp
+var builder = WebApplication.CreateBuilder();
+
+builder.Configuration.AddEnvironmentVariables();
+builder.Services.AddDaprEncryptionClient();
+```
+
+The Dapr Encryption client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound
+requests with the API token header `abc123`.
+
+### Configuration via prefixed Environment Variables
+
+However, in shared-host scenarios where there are multiple applications all running on the same machine without using
+containers or in development environments, it's not uncommon to prefix environment variables. The following example
+assumes that both the HTTP endpoint and the API token will be pulled from environment variables prefixed with the
+value "myapp_". The two environment variables used in this scenario are as follows:
+
+| Key | Value |
+| --- | --- |
+| myapp_DAPR_HTTP_ENDPOINT | http://localhost:54321 |
+| myapp_DAPR_API_TOKEN | abc123 |
+
+These environment variables will be loaded into the registered configuration in the following example and made available
+without the prefix attached.
+
+```csharp
+var builder = WebApplication.CreateBuilder();
+
+builder.Configuration.AddEnvironmentVariables(prefix: "myapp_");
+builder.Services.AddDaprEncryptionClient();
+```
+
+The Dapr Jobs client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound
+requests with the API token header `abc123`.
+
+## Use the Dapr Encryption client without relying on dependency injection
+While the use of dependency injection simplifies the use of complex types in .NET and makes it easier to
+deal with complicated configurations, you're not required to register the `DaprEncryptionClient` in this way. Rather,
+you can also elect to create an instance of it from a `DaprEncryptionClientBuilder` instance as demonstrated below:
+
+```cs
+
+public class MySampleClass
+{
+ public void DoSomething()
+ {
+ var daprEncryptionClientBuilder = new DaprEncryptionClientBuilder();
+ var daprEncryptionClient = daprEncryptionClientBuilder.Build();
+
+ //Do something with the `daprEncryptionClient`
+ }
+}
+```
+
+## Encrypt a byte-array payload
+
+
+## Encrypt a stream-based payload
+
+
+## Decrypt a payload from a byte array
+
+
+## Decrypt a stream-based payload
+
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/dotnet-cryptographyclient-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-cryptography/dotnet-cryptographyclient-usage.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/Client/PublishSubscribe/StreamingSubscriptionExample/Program.cs b/examples/Client/PublishSubscribe/StreamingSubscriptionExample/Program.cs
index ac00d8798..77eebe92e 100644
--- a/examples/Client/PublishSubscribe/StreamingSubscriptionExample/Program.cs
+++ b/examples/Client/PublishSubscribe/StreamingSubscriptionExample/Program.cs
@@ -6,6 +6,20 @@
builder.Services.AddDaprPubSubClient();
var app = builder.Build();
+var messagingClient = app.Services.GetRequiredService();
+
+//Create a dynamic streaming subscription and subscribe with a timeout of 30 seconds and 10 seconds for message handling
+var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+var subscription = await messagingClient.SubscribeAsync("pubsub", "myTopic",
+ new DaprSubscriptionOptions(new MessageHandlingPolicy(TimeSpan.FromSeconds(10), TopicResponseAction.Retry)),
+ HandleMessageAsync, cancellationTokenSource.Token);
+
+await Task.Delay(TimeSpan.FromMinutes(1));
+
+//When you're done with the subscription, simply dispose of it
+await subscription.DisposeAsync();
+return;
+
//Process each message returned from the subscription
Task HandleMessageAsync(TopicMessage message, CancellationToken cancellationToken = default)
{
@@ -20,16 +34,3 @@ Task HandleMessageAsync(TopicMessage message, CancellationT
return Task.FromResult(TopicResponseAction.Retry);
}
}
-
-var messagingClient = app.Services.GetRequiredService();
-
-//Create a dynamic streaming subscription and subscribe with a timeout of 30 seconds and 10 seconds for message handling
-var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
-var subscription = await messagingClient.SubscribeAsync("pubsub", "myTopic",
- new DaprSubscriptionOptions(new MessageHandlingPolicy(TimeSpan.FromSeconds(10), TopicResponseAction.Retry)),
- HandleMessageAsync, cancellationTokenSource.Token);
-
-await Task.Delay(TimeSpan.FromMinutes(1));
-
-//When you're done with the subscription, simply dispose of it
-await subscription.DisposeAsync();
diff --git a/examples/Cryptography/CryptographySample/CryptographySample.csproj b/examples/Cryptography/CryptographySample/CryptographySample.csproj
new file mode 100644
index 000000000..cbb758a11
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/CryptographySample.csproj
@@ -0,0 +1,12 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/examples/Cryptography/CryptographySample/EncryptionOperation.cs b/examples/Cryptography/CryptographySample/EncryptionOperation.cs
new file mode 100644
index 000000000..d69caaf4e
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/EncryptionOperation.cs
@@ -0,0 +1,128 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Security.Cryptography;
+using Dapr.Cryptography.Encryption;
+#pragma warning disable CS0618 // Type or member is obsolete
+
+namespace CryptographySample;
+
+public sealed class EncryptionOperation(ILogger logger, DaprEncryptionClient encryptionClient) : IHostedService
+{
+ private const string VaultComponentName = "myvault";
+ private const string KeyName = "samplekey";
+
+ ///
+ /// Triggered when the application host is ready to start the service.
+ ///
+ /// Indicates that the start process has been aborted.
+ /// A that represents the asynchronous Start operation.
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ //Download a medium file to use for this demonstration
+ var (filePath, checksum) = await DownloadMediumFileAsync();
+ logger.LogInformation("Original checksum: {checksum}", checksum);
+
+ //Load and encrypt the file
+ var encryptedFilePath = await EncryptFileAsync(filePath, cancellationToken);
+
+ //Get the checksum of the encrypted file
+ var encryptedChecksum = await CalculateChecksum(encryptedFilePath);
+ logger.LogInformation("Encrypted checksum: {checksum}", encryptedChecksum);
+
+ //Decrypt the file
+ var decryptedFilePath = await DecryptFileAsync(encryptedFilePath, cancellationToken);
+
+ //Get the decrypted file's checksum
+ var decryptedChecksum = await CalculateChecksum(decryptedFilePath);
+ logger.LogInformation("Decrypted checksum: {checksum}", decryptedChecksum);
+
+ logger.LogInformation("Original and decrypted checksums {evaluationResult} a match!",
+ (string.Equals(checksum, decryptedChecksum) ? "are" : "are NOT"));
+ }
+
+ private async Task EncryptFileAsync(string filePath, CancellationToken cancellationToken)
+ {
+ await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
+ var encryptedBytes = await encryptionClient.EncryptAsync(VaultComponentName, fileStream, KeyName,
+ new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken);
+
+ //Write the encrypted file to the file system
+ const string encryptedFileName = "enc.file";
+ await using var encryptedFileStream = new FileStream(encryptedFileName, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 1024 * 4, useAsync: true);
+ await foreach (var memory in encryptedBytes)
+ {
+ await encryptedFileStream.WriteAsync(memory, cancellationToken);
+ }
+
+ return encryptedFileStream.Name;
+ }
+
+ private async Task DecryptFileAsync(
+ string filePath,
+ CancellationToken cancellationToken)
+ {
+ await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
+ var decryptedBytes = await encryptionClient.DecryptAsync(VaultComponentName, fileStream, KeyName,
+ cancellationToken: cancellationToken);
+
+ //Write the decrypted file to the file system
+ const string decryptedFileName = "dec.file";
+ await using var decryptedFileStream = new FileStream(decryptedFileName, FileMode.Create, FileAccess.Write,
+ FileShare.None, bufferSize: 1024 * 4, useAsync: true);
+ await foreach (var memory in decryptedBytes)
+ {
+ await decryptedFileStream.WriteAsync(memory, cancellationToken);
+ }
+
+ return decryptedFileStream.Name;
+ }
+
+ ///
+ /// Triggered when the application host is performing a graceful shutdown.
+ ///
+ /// Indicates that the shutdown process should no longer be graceful.
+ /// A that represents the asynchronous Stop operation.
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+ ///
+ /// Downloads ZIP file of Dapr Docs repository - ~50MB.
+ ///
+ ///
+ private static async Task<(string filePath, string checksum)> DownloadMediumFileAsync()
+ {
+ const string fileName = "mediumFile.zip";
+ using var httpClient = new HttpClient();
+ var response =
+ await httpClient.GetStreamAsync("https://github.com/dapr/docs/archive/refs/heads/master.zip");
+ await using var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
+ await response.CopyToAsync(fileStream);
+ var checksum = await CalculateChecksum(fileStream.Name);
+ return (fileStream.Name, checksum);
+ }
+
+ ///
+ /// Calculates a checksum for a given file given its path.
+ ///
+ /// The path of the file to evaluate.
+ ///
+ private static async Task CalculateChecksum(string filePath)
+ {
+ await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ using var sha256 = SHA256.Create();
+ var hash = await sha256.ComputeHashAsync(fs);
+ return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
+ }
+}
+
+
diff --git a/examples/Cryptography/CryptographySample/Program.cs b/examples/Cryptography/CryptographySample/Program.cs
new file mode 100644
index 000000000..ddcf72808
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/Program.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Security.Cryptography;
+using CryptographySample;
+using Dapr.Cryptography.Encryption.Extensions;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddHostedService();
+builder.Services.AddDaprEncryptionClient();
+builder.Logging.ClearProviders();
+builder.Logging.AddConsole();
+
+var app = builder.Build();
+
+await app.RunAsync();
diff --git a/examples/Cryptography/CryptographySample/Properties/launchSettings.json b/examples/Cryptography/CryptographySample/Properties/launchSettings.json
new file mode 100644
index 000000000..8616cebc7
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:48832",
+ "sslPort": 44391
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5279",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7204;http://localhost:5279",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/examples/Cryptography/CryptographySample/appsettings.Development.json b/examples/Cryptography/CryptographySample/appsettings.Development.json
new file mode 100644
index 000000000..0c208ae91
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/examples/Cryptography/CryptographySample/appsettings.json b/examples/Cryptography/CryptographySample/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/examples/Cryptography/CryptographySample/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs
index 394b313e2..3e5900333 100644
--- a/src/Dapr.Client/DaprClientGrpc.cs
+++ b/src/Dapr.Client/DaprClientGrpc.cs
@@ -11,8 +11,6 @@
// limitations under the License.
// ------------------------------------------------------------------------
-using Dapr.Common.Extensions;
-
namespace Dapr.Client;
using System;
@@ -23,7 +21,6 @@ namespace Dapr.Client;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -1664,8 +1661,7 @@ public override async Task UnsubscribeConfigur
#region Cryptography
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ [Obsolete("Use `EncryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
public override async Task> EncryptAsync(string vaultResourceName,
ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default)
@@ -1685,11 +1681,13 @@ public override async Task> EncryptAsync(string vaultResour
}
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
- public override async Task>> EncryptAsync(string vaultResourceName,
+ [Obsolete("Use `EncryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
+ public override async Task>> EncryptAsync(
+ string vaultResourceName,
Stream plaintextStream,
- string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default)
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
@@ -1718,53 +1716,60 @@ public override async Task>> EncryptAsync(
var options = CreateCallOptions(headers: null, cancellationToken);
var duplexStream = client.EncryptAlpha1(options);
-
+
//Run both operations at the same time, but return the output of the streaming values coming from the operation
- var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken));
- return await Task.WhenAll(
- //Stream the plaintext data to the sidecar in chunks
- SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes,
- duplexStream, encryptRequestOptions, cancellationToken),
- //At the same time, retrieve the encrypted response from the sidecar
- receiveResult).ContinueWith(_ => receiveResult.Result, cancellationToken);
+ var sendTask = Task.Run(() => SendPlaintextStreamAsync(plaintextStream,
+ encryptionOptions.StreamingBlockSizeInBytes,
+ duplexStream, encryptRequestOptions, cancellationToken), cancellationToken);
+ var receiveTask = Task.Run(() =>
+ RetrieveEncryptedStreamAsync(duplexStream, cancellationToken), cancellationToken);
+
+ await Task.WhenAll(sendTask, receiveTask).ConfigureAwait(false);
+ return receiveTask.Result;
}
///
/// Sends the plaintext bytes in chunks to the sidecar to be encrypted.
///
- private async Task SendPlaintextStreamAsync(Stream plaintextStream,
+ private static async Task SendPlaintextStreamAsync(
+ Stream plaintextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall duplexStream,
Autogenerated.EncryptRequestOptions encryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the encryption request itself in the first message
- await duplexStream.RequestStream.WriteAsync(
- new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken);
+ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken);
- //Send the plaintext bytes in blocks in subsequent messages
+ //Send the ciphertext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(plaintextStream, streamingBlockSizeInBytes))
{
- var buffer = new byte[streamingBlockSizeInBytes];
- int bytesRead;
- ulong sequenceNumber = 0;
-
- while ((bytesRead =
- await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
- cancellationToken)) !=
- 0)
+ var buffer = ArrayPool.Shared.Rent(streamingBlockSizeInBytes);
+ try
{
- await duplexStream.RequestStream.WriteAsync(
- new Autogenerated.EncryptRequest
- {
- Payload = new Autogenerated.StreamPayload
- {
- Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
- }
- }, cancellationToken);
+ int bytesRead;
+ ulong sequenceNumber = 0;
- //Increment the sequence number
- sequenceNumber++;
+ while ((bytesRead =
+ await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
+ cancellationToken)) != 0)
+ {
+ await duplexStream.RequestStream.WriteAsync(
+ new Autogenerated.EncryptRequest
+ {
+ Payload = new Autogenerated.StreamPayload
+ {
+ Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
+ }
+ }, cancellationToken);
+
+ //Increment the sequence number
+ sequenceNumber++;
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
}
}
@@ -1775,7 +1780,7 @@ await duplexStream.RequestStream.WriteAsync(
///
/// Retrieves the encrypted bytes from the encryption operation on the sidecar and returns as an enumerable stream.
///
- private async IAsyncEnumerable> RetrieveEncryptedStreamAsync(
+ private static async IAsyncEnumerable> RetrieveEncryptedStreamAsync(
AsyncDuplexStreamingCall duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
@@ -1787,11 +1792,13 @@ private async IAsyncEnumerable> RetrieveEncryptedStreamAsyn
}
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
- public override async Task>> DecryptAsync(string vaultResourceName,
- Stream ciphertextStream, string keyName,
- DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default)
+ [Obsolete("Use `DecryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
+ public override async Task>> DecryptAsync(
+ string vaultResourceName,
+ Stream ciphertextStream,
+ string keyName,
+ DecryptionOptions decryptionOptions,
+ CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
@@ -1807,20 +1814,18 @@ public override async Task>> DecryptAsync(
var duplexStream = client.DecryptAlpha1(options);
//Run both operations at the same time, but return the output of the streaming values coming from the operation
- var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken));
- return await Task.WhenAll(
- //Stream the ciphertext data to the sidecar in chunks
- SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes,
- duplexStream, decryptRequestOptions, cancellationToken),
- //At the same time, retrieve the decrypted response from the sidecar
- receiveResult)
- //Return only the result of the `RetrieveEncryptedStreamAsync` method
- .ContinueWith(t => receiveResult.Result, cancellationToken);
+ var sendTask = Task.Run(() => SendCiphertextStreamAsync(ciphertextStream,
+ decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken),
+ cancellationToken);
+ var receiveTask = Task.Run(() =>
+ RetrieveDecryptedStreamAsync(duplexStream, cancellationToken), cancellationToken);
+
+ await Task.WhenAll(sendTask, receiveTask).ConfigureAwait(false);
+ return receiveTask.Result;
}
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ [Obsolete("Use `DecryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
public override Task>> DecryptAsync(string vaultResourceName,
Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) =>
DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(),
@@ -1829,41 +1834,51 @@ public override Task>> DecryptAsync(string
///
/// Sends the ciphertext bytes in chunks to the sidecar to be decrypted.
///
- private async Task SendCiphertextStreamAsync(Stream ciphertextStream,
+ private static async Task SendCiphertextStreamAsync(Stream ciphertextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall duplexStream,
Autogenerated.DecryptRequestOptions decryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the decryption request itself in the first message
- await duplexStream.RequestStream.WriteAsync(
- new Autogenerated.DecryptRequest { Options = decryptRequestOptions }, cancellationToken);
-
+ var request = new Autogenerated.DecryptRequest();
+ if (decryptRequestOptions is not null)
+ {
+ request.Options = decryptRequestOptions;
+ }
+ await duplexStream.RequestStream.WriteAsync(request, cancellationToken);
+
//Send the ciphertext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(ciphertextStream, streamingBlockSizeInBytes))
{
- var buffer = new byte[streamingBlockSizeInBytes];
- int bytesRead;
- ulong sequenceNumber = 0;
-
- while ((bytesRead =
- await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
- cancellationToken)) != 0)
+ var buffer = ArrayPool.Shared.Rent(streamingBlockSizeInBytes);
+ try
{
- await duplexStream.RequestStream.WriteAsync(
- new Autogenerated.DecryptRequest
- {
- Payload = new Autogenerated.StreamPayload
- {
- Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
- }
- }, cancellationToken);
+ int bytesRead;
+ ulong sequenceNumber = 0;
- //Increment the sequence number
- sequenceNumber++;
+ while ((bytesRead = await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
+ cancellationToken: cancellationToken)) != 0)
+ {
+ await duplexStream.RequestStream.WriteAsync(
+ new Autogenerated.DecryptRequest
+ {
+ Payload = new Autogenerated.StreamPayload
+ {
+ Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
+ }
+ }, cancellationToken);
+
+ //Increment the sequence number
+ sequenceNumber++;
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
}
}
-
+
//Send the completion message
await duplexStream.RequestStream.CompleteAsync();
}
@@ -1871,7 +1886,7 @@ await duplexStream.RequestStream.WriteAsync(
///
/// Retrieves the decrypted bytes from the decryption operation on the sidecar and returns as an enumerable stream.
///
- private async IAsyncEnumerable> RetrieveDecryptedStreamAsync(
+ private static async IAsyncEnumerable> RetrieveDecryptedStreamAsync(
AsyncDuplexStreamingCall duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
@@ -1883,8 +1898,7 @@ private async IAsyncEnumerable> RetrieveDecryptedStreamAsyn
}
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ [Obsolete("Use `DecryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
public override async Task> DecryptAsync(string vaultResourceName,
ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions,
CancellationToken cancellationToken = default)
@@ -1904,8 +1918,7 @@ public override async Task> DecryptAsync(string vaultResour
}
///
- [Obsolete(
- "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ [Obsolete("Use `DecryptAsync` from the `Dapr.Configuration` NuGet package instead as this method will be removed from `Dapr.Client` with the release of v1.17")]
public override async Task> DecryptAsync(string vaultResourceName,
ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default) =>
await DecryptAsync(vaultResourceName, ciphertextBytes, keyName,
diff --git a/src/Dapr.Common/AssemblyInfo.cs b/src/Dapr.Common/AssemblyInfo.cs
index 3037485a9..d0698cbbd 100644
--- a/src/Dapr.Common/AssemblyInfo.cs
+++ b/src/Dapr.Common/AssemblyInfo.cs
@@ -19,6 +19,7 @@
[assembly: InternalsVisibleTo("Dapr.AI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.Cryptography, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
@@ -34,6 +35,7 @@
[assembly: InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest.App, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.Crytography.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Common.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.E2E.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.E2E.Test.Actors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
diff --git a/src/Dapr.Cryptography/AssemblyInfo.cs b/src/Dapr.Cryptography/AssemblyInfo.cs
new file mode 100644
index 000000000..4294b4d4f
--- /dev/null
+++ b/src/Dapr.Cryptography/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Dapr.Cryptography.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
diff --git a/src/Dapr.Cryptography/Dapr.Cryptography.csproj b/src/Dapr.Cryptography/Dapr.Cryptography.csproj
new file mode 100644
index 000000000..e2f3fdc76
--- /dev/null
+++ b/src/Dapr.Cryptography/Dapr.Cryptography.csproj
@@ -0,0 +1,25 @@
+
+
+
+ enable
+ enable
+ This package contains the reference assemblies for developing services using Cryptography using Dapr.
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
diff --git a/src/Dapr.Cryptography/Encryption/DaprDecryptionProcessor.cs b/src/Dapr.Cryptography/Encryption/DaprDecryptionProcessor.cs
new file mode 100644
index 000000000..9b3319a0f
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprDecryptionProcessor.cs
@@ -0,0 +1,95 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using Google.Protobuf;
+using Grpc.Core;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// Processor for handling Dapr decryption pipeline-based operations.
+///
+internal sealed class DaprDecryptionProcessor : DaprEncryptionPipelineProcessorBase
+{
+ ///
+ /// Sends the stream from the SDK to the Dapr sidecar.
+ ///
+ /// The stream containing the data to be processed.
+ /// The size of the blocks to be read from the stream.
+ /// The duplex stream used for sending and receiving data.
+ /// The options for the request.
+ /// A token to monitor for cancellation requests.
+ /// A task representing the asynchronous operation.
+ protected override async Task SendStreamAsync(
+ Stream stream,
+ int blockSize,
+ AsyncDuplexStreamingCall duplexStream,
+ Autogenerated.DecryptRequestOptions? options,
+ CancellationToken cancellationToken)
+ {
+ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest { Options = options },
+ cancellationToken);
+
+ await using (var bufferedStream = new BufferedStream(stream, blockSize))
+ {
+ var buffer = ArrayPool.Shared.Rent(blockSize);
+ try
+ {
+ int bytesRead;
+ ulong sequenceNumber = 0;
+
+ while ((bytesRead = await bufferedStream.ReadAsync(buffer.AsMemory(0, blockSize), cancellationToken)) !=
+ 0)
+ {
+ await duplexStream.RequestStream.WriteAsync(
+ new Autogenerated.DecryptRequest
+ {
+ Payload = new Autogenerated.StreamPayload
+ {
+ Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
+ }
+ }, cancellationToken);
+
+ //Increment the sequence number
+ sequenceNumber++;
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+
+ await duplexStream.RequestStream.CompleteAsync();
+ }
+
+ ///
+ /// Retrieves the processed stream data from the Dapr sidecar.
+ ///
+ /// The duplex stream used for receiving data.
+ /// A token to monitor for cancellation requests.
+ /// An asynchronous enumerable of the processed data.
+ protected override async IAsyncEnumerable> RetrieveStreamAsync(
+ AsyncDuplexStreamingCall duplexStream,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
+ .ConfigureAwait(false))
+ {
+ yield return encryptResponse.Payload.Data.Memory;
+ }
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DaprEncryptionClient.cs b/src/Dapr.Cryptography/Encryption/DaprEncryptionClient.cs
new file mode 100644
index 000000000..7be80c71f
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprEncryptionClient.cs
@@ -0,0 +1,112 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// The base implementation of the Dapr encryption/decryption client.
+///
+public abstract class DaprEncryptionClient : IDisposable, IDaprEncryptionClient
+{
+ private bool disposed;
+
+ ///
+ /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The bytes of the plaintext value to encrypt.
+ /// The name of the key to use from the Vault for the encryption operation.
+ /// Options informing how the encryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of encrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public abstract Task> EncryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory plaintextBytes,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Encrypts a stream using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The stream containing the bytes of the plaintext value to encrypt.
+ /// The name of the key to use from the Vault for the encryption operation.
+ /// Options informing how the encryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of encrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public abstract IAsyncEnumerable> EncryptAsync(
+ string vaultResourceName,
+ Stream plaintextStream,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The bytes of the ciphertext value to decrypt.
+ /// The name of the key to use from the Vault for the decryption operation.
+ /// Options informing how the decryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of decrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public abstract Task> DecryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory ciphertextBytes,
+ string keyName,
+ DecryptionOptions? options = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The stream containing the bytes of the ciphertext value to decrypt.
+ /// The name of the key to use from the Vault for the decryption operation.
+ /// Options informing how the decryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An asynchronously enumerable array of decrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public abstract IAsyncEnumerable> DecryptAsync(
+ string vaultResourceName,
+ Stream ciphertextStream,
+ string keyName,
+ DecryptionOptions? options = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ public void Dispose()
+ {
+ if (!this.disposed)
+ {
+ Dispose(disposing: true);
+ this.disposed = true;
+ }
+ }
+
+ ///
+ /// Disposes the resources associated with the object.
+ ///
+ /// true if called by a call to the Dispose method; otherwise false.
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DaprEncryptionClientBuilder.cs b/src/Dapr.Cryptography/Encryption/DaprEncryptionClientBuilder.cs
new file mode 100644
index 000000000..2e88e52e0
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprEncryptionClientBuilder.cs
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common;
+using Microsoft.Extensions.Configuration;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// Builds a .
+///
+/// An optional instance of .
+public sealed class DaprEncryptionClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder(configuration)
+{
+ ///
+ /// Builds the client instance from the properties of the builder.
+ ///
+ /// The Dapr client instance.
+ ///
+ /// Builds the client instance from the properties of the builder.
+ ///
+ public override DaprEncryptionClient Build()
+ {
+ var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprEncryptionClient).Assembly);
+ var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel);
+ return new DaprEncryptionGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken);
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DaprEncryptionGrpcClient.cs b/src/Dapr.Cryptography/Encryption/DaprEncryptionGrpcClient.cs
new file mode 100644
index 000000000..bc7cc416f
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprEncryptionGrpcClient.cs
@@ -0,0 +1,180 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using Dapr.Common;
+using Dapr.Common.Extensions;
+using P = Dapr.Client.Autogen.Grpc.v1.Dapr;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// A client for interacting with the Dapr cryptography encryption and decryption endpoints.
+///
+internal sealed class DaprEncryptionGrpcClient(P.DaprClient client, HttpClient httpClient, string? daprApiToken) : DaprEncryptionClient
+{
+ ///
+ /// The HTTP client used by the client for calling the Dapr runtime.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly HttpClient HttpClient = httpClient;
+ ///
+ /// The Dapr API token value.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly string? DaprApiToken = daprApiToken;
+
+ ///
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public override async Task> EncryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory plaintextBytes,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default)
+ {
+ using var memoryStream = new MemoryStream(plaintextBytes.Length);
+ await memoryStream.WriteAsync(plaintextBytes, cancellationToken);
+ memoryStream.Position = 0;
+
+ var encryptionResult = EncryptAsync(vaultResourceName, memoryStream, keyName, encryptionOptions, cancellationToken);
+
+ var bufferedResult = new ArrayBufferWriter();
+ await foreach (var item in encryptionResult)
+ {
+ bufferedResult.Write(item.Span);
+ }
+
+ return bufferedResult.WrittenMemory;
+ }
+
+ ///
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public override async IAsyncEnumerable> EncryptAsync(
+ string vaultResourceName,
+ Stream plaintextStream,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
+ ArgumentException.ThrowIfNullOrEmpty(keyName, nameof(keyName));
+ ArgumentNullException.ThrowIfNull(plaintextStream, nameof(plaintextStream));
+ ArgumentNullException.ThrowIfNull(encryptionOptions, nameof(encryptionOptions));
+
+ var duplexStream = client.EncryptAlpha1(DaprClientUtilities.ConfigureGrpcCallOptions(
+ typeof(DaprEncryptionClient).Assembly,
+ this.DaprApiToken, cancellationToken));
+ var processor = new DaprEncryptionProcessor();
+
+ await foreach (var encryptedData in processor.ProcessStreamAsync(
+ plaintextStream,
+ encryptionOptions.StreamingBlockSizeInBytes,
+ duplexStream,
+ new Autogenerated.EncryptRequest
+ {
+ Options = new Autogenerated.EncryptRequestOptions
+ {
+ ComponentName = vaultResourceName,
+ DataEncryptionCipher = encryptionOptions.EncryptionCipher.GetValueFromEnumMember(),
+ KeyName = keyName,
+ DecryptionKeyName = encryptionOptions.DecryptionKeyName,
+ KeyWrapAlgorithm = encryptionOptions.KeyWrapAlgorithm.GetValueFromEnumMember(),
+ OmitDecryptionKeyName =
+ string.IsNullOrWhiteSpace(encryptionOptions.DecryptionKeyName)
+ }
+ },
+ cancellationToken))
+ {
+ yield return encryptedData;
+ }
+ }
+
+ ///
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public override async Task> DecryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory ciphertextBytes,
+ string keyName,
+ DecryptionOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ using var memoryStream = new MemoryStream(ciphertextBytes.Length);
+ await memoryStream.WriteAsync(ciphertextBytes, cancellationToken);
+ memoryStream.Position = 0;
+
+ var decryptionResult = DecryptAsync(vaultResourceName, memoryStream, keyName, options, cancellationToken);
+
+ var bufferedResult = new ArrayBufferWriter();
+ await foreach(var item in decryptionResult)
+ {
+ bufferedResult.Write(item.Span);
+ }
+
+ return bufferedResult.WrittenMemory;
+ }
+
+ ///
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public override async IAsyncEnumerable> DecryptAsync(
+ string vaultResourceName,
+ Stream ciphertextStream,
+ string keyName,
+ DecryptionOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
+ ArgumentNullException.ThrowIfNull(ciphertextStream, nameof(ciphertextStream));
+ ArgumentException.ThrowIfNullOrEmpty(keyName, nameof(keyName));
+
+ options ??= new DecryptionOptions();
+
+ var duplexStream = client.DecryptAlpha1(DaprClientUtilities.ConfigureGrpcCallOptions(
+ typeof(DaprEncryptionClient).Assembly,
+ this.DaprApiToken, cancellationToken));
+ var processor = new DaprDecryptionProcessor();
+
+ await foreach (var decryptedData in processor.ProcessStreamAsync(
+ ciphertextStream,
+ options.StreamingBlockSizeInBytes,
+ duplexStream,
+ new Autogenerated.DecryptRequest
+ {
+ Options = new Autogenerated.DecryptRequestOptions
+ {
+ ComponentName = vaultResourceName, KeyName = keyName
+ }
+ }, cancellationToken))
+ {
+ yield return decryptedData;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ this.HttpClient.Dispose();
+ }
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DaprEncryptionPipelineProcessorBase.cs b/src/Dapr.Cryptography/Encryption/DaprEncryptionPipelineProcessorBase.cs
new file mode 100644
index 000000000..1af792a31
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprEncryptionPipelineProcessorBase.cs
@@ -0,0 +1,120 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using Grpc.Core;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// Base class for process streams of data for both encryption and decryption operations.
+///
+/// The type of request message presented to the Dapr sidecar for the operation.
+/// The type of request message options presented to the Dapr sidecar to configure the operation.
+/// The type of response message provided by the Dapr sidecar for the operation.
+internal abstract class DaprEncryptionPipelineProcessorBase
+{
+ ///
+ /// Sends the stream from the SDK to the Dapr sidecar.
+ ///
+ /// The stream containing the data to be processed.
+ /// The size of the blocks to be read from the stream.
+ /// The duplex stream used for sending and receiving data.
+ /// The options for the request.
+ /// A token to monitor for cancellation requests.
+ /// A task representing the asynchronous operation.
+ protected abstract Task SendStreamAsync(
+ Stream stream,
+ int blockSize,
+ AsyncDuplexStreamingCall duplexStream,
+ TRequestOptions options,
+ CancellationToken cancellationToken);
+
+ ///
+ /// Retrieves the processed stream data from the Dapr sidecar.
+ ///
+ /// The duplex stream used for receiving data.
+ /// A token to monitor for cancellation requests.
+ /// An asynchronous enumerable of the processed data.
+ protected abstract IAsyncEnumerable> RetrieveStreamAsync(
+ AsyncDuplexStreamingCall duplexStream,
+ CancellationToken cancellationToken);
+
+ ///
+ /// Processes the stream by reading from the input stream and writing to the duplex stream.
+ ///
+ /// The stream containing the data to be processed.
+ /// The size of the blocks to be read from the stream.
+ /// The duplex stream used for sending and receiving data.
+ /// The request to the sidecar.
+ /// A token to monitor for cancellation requests.
+ /// An asynchronous enumerable of the processed data.
+ public async IAsyncEnumerable> ProcessStreamAsync
+ (Stream stream,
+ int blockSize,
+ AsyncDuplexStreamingCall duplexStream,
+ TRequest request,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var pipe = new Pipe();
+ var writer = FillPipeAsync(pipe.Writer, stream, blockSize, request, duplexStream, cancellationToken);
+ var reading = RetrieveStreamAsync(duplexStream, cancellationToken);
+
+ var writingTask = Task.Run(() => writer, cancellationToken);
+
+ await foreach (var data in reading)
+ {
+ yield return data;
+ }
+
+ await writingTask.ConfigureAwait(false);
+ }
+
+ ///
+ /// Fills the pipe by reading from the input stream and writing to the pipe.
+ ///
+ /// The pipe writer to write the data to.
+ /// The stream containing the source data to be processed.
+ /// The size of the blocks to be read from the input stream.
+ /// The input request.
+ /// The duplex stream used for sending encryption data.
+ /// A token to monitor for cancellation requests.
+ private static async Task FillPipeAsync(
+ PipeWriter writer,
+ Stream stream,
+ int blockSize,
+ TRequest request,
+ AsyncDuplexStreamingCall duplexStream,
+ CancellationToken cancellationToken)
+ {
+ await duplexStream.RequestStream.WriteAsync(request, cancellationToken);
+
+ while (true)
+ {
+ var memory = writer.GetMemory(blockSize);
+ var bytesRead = await stream.ReadAsync(memory, cancellationToken);
+ if (bytesRead == 0)
+ break;
+
+ writer.Advance(bytesRead);
+
+ var result = await writer.FlushAsync(cancellationToken);
+ if (result.IsCompleted)
+ break;
+ }
+
+ await writer.CompleteAsync();
+ await duplexStream.RequestStream.CompleteAsync();
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DaprEncryptionProcessor.cs b/src/Dapr.Cryptography/Encryption/DaprEncryptionProcessor.cs
new file mode 100644
index 000000000..6245a985e
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DaprEncryptionProcessor.cs
@@ -0,0 +1,95 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using Google.Protobuf;
+using Grpc.Core;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// Processor for handling Dapr encryption pipeline-based operations.
+///
+internal sealed class DaprEncryptionProcessor : DaprEncryptionPipelineProcessorBase
+{
+ ///
+ /// Sends the stream from the SDK to the Dapr sidecar.
+ ///
+ /// The stream containing the data to be processed.
+ /// The size of the blocks to be read from the stream.
+ /// The duplex stream used for sending and receiving data.
+ /// The options for the request.
+ /// A token to monitor for cancellation requests.
+ /// A task representing the asynchronous operation.
+ protected override async Task SendStreamAsync(
+ Stream stream,
+ int blockSize,
+ AsyncDuplexStreamingCall duplexStream,
+ Autogenerated.EncryptRequestOptions options,
+ CancellationToken cancellationToken)
+ {
+ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest { Options = options },
+ cancellationToken);
+
+ await using (var bufferedStream = new BufferedStream(stream, blockSize))
+ {
+ var buffer = ArrayPool.Shared.Rent(blockSize);
+ try
+ {
+ int bytesRead;
+ ulong sequenceNumber = 0;
+
+ while ((bytesRead = await bufferedStream.ReadAsync(buffer.AsMemory(0, blockSize), cancellationToken)) !=
+ 0)
+ {
+ await duplexStream.RequestStream.WriteAsync(
+ new Autogenerated.EncryptRequest
+ {
+ Payload = new Autogenerated.StreamPayload
+ {
+ Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
+ }
+ }, cancellationToken);
+
+ //Increment the sequence number
+ sequenceNumber++;
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+
+ await duplexStream.RequestStream.CompleteAsync();
+ }
+
+ ///
+ /// Retrieves the processed stream data from the Dapr sidecar.
+ ///
+ /// The duplex stream used for receiving data.
+ /// A token to monitor for cancellation requests.
+ /// An asynchronous enumerable of the processed data.
+ protected override async IAsyncEnumerable> RetrieveStreamAsync(
+ AsyncDuplexStreamingCall duplexStream,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
+ .ConfigureAwait(false))
+ {
+ yield return encryptResponse.Payload.Data.Memory;
+ }
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/DataEncryptionCipher.cs b/src/Dapr.Cryptography/Encryption/DataEncryptionCipher.cs
new file mode 100644
index 000000000..2d07e5621
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DataEncryptionCipher.cs
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Runtime.Serialization;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// The cipher used for data encryption operations.
+///
+public enum DataEncryptionCipher
+{
+ ///
+ /// The default data encryption cipher used, this represents AES GCM.
+ ///
+ [EnumMember(Value = "aes-gcm")]
+ AesGcm,
+ ///
+ /// Represents the ChaCha20-Poly1305 data encryption cipher.
+ ///
+ [EnumMember(Value = "chacha20-poly1305")]
+ ChaCha20Poly1305
+}
diff --git a/src/Dapr.Cryptography/Encryption/DecryptionOptions.cs b/src/Dapr.Cryptography/Encryption/DecryptionOptions.cs
new file mode 100644
index 000000000..646726889
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/DecryptionOptions.cs
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// A collection of options used to configure how decryption cryptographic operations are performed.
+///
+public sealed class DecryptionOptions
+{
+ private int streamingBlockSizeInBytes = 4 * 1024; // 4KB
+
+ ///
+ /// The size of the block in bytes used to send data to the sidecar for cryptography operations.
+ ///
+ /// Thrown if a number less than or equal to zero
+ /// is used for the block size.
+ public int StreamingBlockSizeInBytes
+ {
+ get => streamingBlockSizeInBytes;
+ set
+ {
+ if (value <= 0)
+ throw new ArgumentOutOfRangeException(nameof(value));
+
+ streamingBlockSizeInBytes = value;
+ }
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/EncryptionOptions.cs b/src/Dapr.Cryptography/Encryption/EncryptionOptions.cs
new file mode 100644
index 000000000..ab48ac159
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/EncryptionOptions.cs
@@ -0,0 +1,65 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// A collection of options used to configure how encryption cryptographic operations are performed.
+///
+public class EncryptionOptions
+{
+ ///
+ /// Creates a new instance of the .
+ ///
+ ///
+ public EncryptionOptions(KeyWrapAlgorithm keyWrapAlgorithm)
+ {
+ KeyWrapAlgorithm = keyWrapAlgorithm;
+ }
+
+ ///
+ /// The name of the algorithm used to wrap the encryption key.
+ ///
+ public KeyWrapAlgorithm KeyWrapAlgorithm { get; set; }
+
+ private int streamingBlockSizeInBytes = 32 * 1024; // 32 KB
+ ///
+ /// The size of the block in bytes used to send data to the sidecar for cryptography operations.
+ ///
+ ///
+ /// This defaults to 4KB and generally should not exceed 64KB.
+ ///
+ public int StreamingBlockSizeInBytes
+ {
+ get => streamingBlockSizeInBytes;
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ streamingBlockSizeInBytes = value;
+ }
+ }
+
+ ///
+ /// The optional name (and optionally a version) of the key specified to use during decryption.
+ ///
+ public string DecryptionKeyName { get; set; } = string.Empty;
+
+ ///
+ /// The name of the cipher to use for the encryption operation.
+ ///
+ public DataEncryptionCipher EncryptionCipher { get; set; } = DataEncryptionCipher.AesGcm;
+}
diff --git a/src/Dapr.Cryptography/Encryption/Extensions/EncryptionServiceCollectionExtensions.cs b/src/Dapr.Cryptography/Encryption/Extensions/EncryptionServiceCollectionExtensions.cs
new file mode 100644
index 000000000..866d92599
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/Extensions/EncryptionServiceCollectionExtensions.cs
@@ -0,0 +1,77 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Dapr.Cryptography.Encryption.Extensions;
+
+///
+/// Contains extension methods for using Dapr Encryption/Decryption capabilities with dependency injection.
+///
+public static class EncryptionServiceCollectionExtensions
+{
+ ///
+ /// Adds Dapr encryption/decryption support to the service collection.
+ ///
+ /// The .
+ /// Optionally allows greater configuration of the .
+ /// The lifetime of the registered services.
+ ///
+ public static IServiceCollection AddDaprEncryptionClient(
+ this IServiceCollection services,
+ Action? configure = null,
+ ServiceLifetime lifetime = ServiceLifetime.Singleton)
+ {
+ ArgumentNullException.ThrowIfNull(services, nameof(services));
+
+ //Register the IHttpClientFactory implementation
+ services.AddHttpClient();
+
+ var registration = new Func(serviceProvider =>
+ {
+ var httpClientFactory = serviceProvider.GetRequiredService();
+ var configuration = serviceProvider.GetService();
+
+ var builder = new DaprEncryptionClientBuilder(configuration);
+ builder.UseHttpClientFactory(httpClientFactory);
+
+ configure?.Invoke(serviceProvider, builder);
+
+ return builder.Build();
+ });
+
+ switch (lifetime)
+ {
+ case ServiceLifetime.Scoped:
+ services.TryAddScoped(registration);
+ // ReSharper disable once RedundantTypeArgumentsOfMethod
+ services.TryAddScoped(registration);
+ break;
+ case ServiceLifetime.Transient:
+ services.TryAddTransient(registration);
+ // ReSharper disable once RedundantTypeArgumentsOfMethod
+ services.TryAddTransient(registration);
+ break;
+ default:
+ case ServiceLifetime.Singleton:
+ services.TryAddSingleton(registration);
+ // ReSharper disable once RedundantTypeArgumentsOfMethod
+ services.TryAddSingleton(registration);
+ break;
+ }
+
+ return services;
+ }
+}
diff --git a/src/Dapr.Cryptography/Encryption/IDaprEncryptionClient.cs b/src/Dapr.Cryptography/Encryption/IDaprEncryptionClient.cs
new file mode 100644
index 000000000..441505ff6
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/IDaprEncryptionClient.cs
@@ -0,0 +1,92 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// Provides the methods required to implement a .
+///
+public interface IDaprEncryptionClient
+{
+ ///
+ /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The bytes of the plaintext value to encrypt.
+ /// The name of the key to use from the Vault for the encryption operation.
+ /// Options informing how the encryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of encrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public Task> EncryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory plaintextBytes,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Encrypts a stream using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The stream containing the bytes of the plaintext value to encrypt.
+ /// The name of the key to use from the Vault for the encryption operation.
+ /// Options informing how the encryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of encrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public IAsyncEnumerable> EncryptAsync(
+ string vaultResourceName,
+ Stream plaintextStream,
+ string keyName,
+ EncryptionOptions encryptionOptions,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The bytes of the ciphertext value to decrypt.
+ /// The name of the key to use from the Vault for the decryption operation.
+ /// Options informing how the decryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An array of decrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public Task> DecryptAsync(
+ string vaultResourceName,
+ ReadOnlyMemory ciphertextBytes,
+ string keyName,
+ DecryptionOptions? options = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
+ ///
+ /// The name of the vault resource used by the operation.
+ /// The stream containing the bytes of the ciphertext value to decrypt.
+ /// The name of the key to use from the Vault for the decryption operation.
+ /// Options informing how the decryption operation should be configured.
+ /// A that can be used to cancel the operation.
+ /// An asynchronously enumerable array of decrypted bytes.
+ [Obsolete(
+ "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
+ public IAsyncEnumerable> DecryptAsync(
+ string vaultResourceName,
+ Stream ciphertextStream,
+ string keyName,
+ DecryptionOptions? options = null,
+ CancellationToken cancellationToken = default);
+}
diff --git a/src/Dapr.Cryptography/Encryption/KeyWrapAlgorithm.cs b/src/Dapr.Cryptography/Encryption/KeyWrapAlgorithm.cs
new file mode 100644
index 000000000..06fa315ea
--- /dev/null
+++ b/src/Dapr.Cryptography/Encryption/KeyWrapAlgorithm.cs
@@ -0,0 +1,58 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Runtime.Serialization;
+
+namespace Dapr.Cryptography.Encryption;
+
+///
+/// The algorithm used for key wrapping cryptographic operations.
+///
+public enum KeyWrapAlgorithm
+{
+ ///
+ /// Represents the AES key wrap algorithm.
+ ///
+ [EnumMember(Value="A256KW")]
+ Aes,
+ ///
+ /// An alias for the AES key wrap algorithm.
+ ///
+ [EnumMember(Value="A256KW")]
+ A256kw,
+ ///
+ /// Represents the AES 128 CBC key wrap algorithm.
+ ///
+ [EnumMember(Value="A128CBC")]
+ A128cbc,
+ ///
+ /// Represents the AES 192 CBC key wrap algorithm.
+ ///
+ [EnumMember(Value="A192CBC")]
+ A192cbc,
+ ///
+ /// Represents the AES 256 CBC key wrap algorithm.
+ ///
+ [EnumMember(Value="A256CBC")]
+ A256cbc,
+ ///
+ /// Represents the RSA key wrap algorithm.
+ ///
+ [EnumMember(Value= "RSA-OAEP-256")]
+ Rsa,
+ ///
+ /// An alias for the RSA key wrap algorithm.
+ ///
+ [EnumMember(Value= "RSA-OAEP-256")]
+ RsaOaep256 //Alias for RSA
+}
diff --git a/src/Dapr.Cryptography/Extensions/AsyncEnumerableExtensions.cs b/src/Dapr.Cryptography/Extensions/AsyncEnumerableExtensions.cs
new file mode 100644
index 000000000..987d5c22c
--- /dev/null
+++ b/src/Dapr.Cryptography/Extensions/AsyncEnumerableExtensions.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Cryptography.Extensions;
+
+internal static class AsyncEnumerableExtensions
+{
+ public static async IAsyncEnumerable Empty()
+ {
+ await Task.CompletedTask;
+ yield break;
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Dapr.Cryptography.Test.csproj b/test/Dapr.Cryptography.Test/Dapr.Cryptography.Test.csproj
new file mode 100644
index 000000000..f6b0fba19
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Dapr.Cryptography.Test.csproj
@@ -0,0 +1,28 @@
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionClientBuilderTests.cs b/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionClientBuilderTests.cs
new file mode 100644
index 000000000..08974f8a5
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionClientBuilderTests.cs
@@ -0,0 +1,48 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Cryptography.Encryption;
+using Microsoft.Extensions.Configuration;
+using Moq;
+
+namespace Dapr.Cryptography.Test.Encryption;
+
+public class DaprEncryptionClientBuilderTests
+{
+ [Fact]
+ public void Build_ShouldReturnNonNullDaprEncryptionClient()
+ {
+ // Arrange
+ var builder = new DaprEncryptionClientBuilder();
+
+ // Act
+ var client = builder.Build();
+
+ // Assert
+ Assert.NotNull(client);
+ }
+
+ [Fact]
+ public void Build_ShouldHandleConfiguration()
+ {
+ // Arrange
+ var mockConfiguration = new Mock();
+ var builder = new DaprEncryptionClientBuilder(mockConfiguration.Object);
+
+ // Act
+ var client = builder.Build();
+
+ // Assert
+ Assert.NotNull(client);
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionGrpcClientTests.cs b/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionGrpcClientTests.cs
new file mode 100644
index 000000000..7acaa21c8
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Encryption/DaprEncryptionGrpcClientTests.cs
@@ -0,0 +1,95 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using Dapr.Cryptography.Encryption;
+using Moq;
+// ReSharper disable UnusedVariable
+
+namespace Dapr.Cryptography.Test.Encryption;
+
+public class DaprEncryptionGrpcClientTests
+{
+ [Fact]
+ public void EncryptAsync_VaultNameCannotBeEmpty()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprEncryptionGrpcClient(mockClient, httpClient, null);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ var result = Assert.ThrowsAsync(async () =>
+ {
+ var bytes = Array.Empty();
+ await client.EncryptAsync(string.Empty, bytes, "key", new EncryptionOptions(KeyWrapAlgorithm.A128cbc), CancellationToken.None);
+
+ });
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ [Fact]
+ public void EncryptAsync_KeyNameCannotBeEmpty()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprEncryptionGrpcClient(mockClient, httpClient, null);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ var result = Assert.ThrowsAsync(async () =>
+ {
+ var bytes = Array.Empty();
+ await client.EncryptAsync("myVault", bytes, string.Empty, new EncryptionOptions(KeyWrapAlgorithm.A128cbc), CancellationToken.None);
+
+ });
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ [Fact]
+ public void EncryptAsync_StreamCannotBeNull()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprEncryptionGrpcClient(mockClient, httpClient, null);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ var result = Assert.ThrowsAsync(async () =>
+ {
+ // ReSharper disable once AssignNullToNotNullAttribute
+ await client.EncryptAsync("myVault", (Stream)null, string.Empty, new EncryptionOptions(KeyWrapAlgorithm.A128cbc), CancellationToken.None);
+ });
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ [Fact]
+ public void EncryptAsync_OptionsCannotBeNull()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprEncryptionGrpcClient(mockClient, httpClient, null);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ var result = Assert.ThrowsAsync(async () =>
+ {
+ // ReSharper disable once AssignNullToNotNullAttribute
+ await client.EncryptAsync("myVault", Array.Empty(), string.Empty, null, CancellationToken.None);
+ });
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Encryption/DecryptionOptionsTests.cs b/test/Dapr.Cryptography.Test/Encryption/DecryptionOptionsTests.cs
new file mode 100644
index 000000000..b4830081c
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Encryption/DecryptionOptionsTests.cs
@@ -0,0 +1,59 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System;
+using Dapr.Cryptography.Encryption;
+
+namespace Dapr.Cryptography.Test.Encryption;
+
+public class DecryptionOptionsTests
+{
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldReturnDefaultValue()
+ {
+ // Arrange
+ var options = new DecryptionOptions();
+
+ // Act
+ var blockSize = options.StreamingBlockSizeInBytes;
+
+ // Assert
+ Assert.Equal(4 * 1024, blockSize); // Default value is 4KB
+ }
+
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldSetValidValue()
+ {
+ // Arrange
+ var options = new DecryptionOptions();
+ const int newBlockSize = 8 * 1024; // 8KB
+
+ // Act
+ options.StreamingBlockSizeInBytes = newBlockSize;
+ var blockSize = options.StreamingBlockSizeInBytes;
+
+ // Assert
+ Assert.Equal(newBlockSize, blockSize);
+ }
+
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldThrowArgumentOutOfRangeException_ForInvalidValue()
+ {
+ // Arrange
+ var options = new DecryptionOptions();
+
+ // Act & Assert
+ Assert.Throws(() => options.StreamingBlockSizeInBytes = 0);
+ Assert.Throws(() => options.StreamingBlockSizeInBytes = -1);
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Encryption/EncryptionOptionsTests.cs b/test/Dapr.Cryptography.Test/Encryption/EncryptionOptionsTests.cs
new file mode 100644
index 000000000..e51f196e2
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Encryption/EncryptionOptionsTests.cs
@@ -0,0 +1,98 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System;
+using Dapr.Cryptography.Encryption;
+
+namespace Dapr.Cryptography.Test.Encryption;
+
+public class EncryptionOptionsTests
+{
+ [Fact]
+ public void Constructor_ShouldInitializeKeyWrapAlgorithm()
+ {
+ // Arrange
+ const KeyWrapAlgorithm keyWrapAlgorithm = KeyWrapAlgorithm.RsaOaep256;
+
+ // Act
+ var options = new EncryptionOptions(keyWrapAlgorithm);
+
+ // Assert
+ Assert.Equal(keyWrapAlgorithm, options.KeyWrapAlgorithm);
+ }
+
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldReturnDefaultValue()
+ {
+ // Arrange
+ var options = new EncryptionOptions(KeyWrapAlgorithm.RsaOaep256);
+
+ // Act
+ var blockSize = options.StreamingBlockSizeInBytes;
+
+ // Assert
+ Assert.Equal(4 * 1024, blockSize); // Default value is 4KB
+ }
+
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldSetValidValue()
+ {
+ // Arrange
+ var options = new EncryptionOptions(KeyWrapAlgorithm.RsaOaep256);
+ const int newBlockSize = 8 * 1024; // 8KB
+
+ // Act
+ options.StreamingBlockSizeInBytes = newBlockSize;
+ var blockSize = options.StreamingBlockSizeInBytes;
+
+ // Assert
+ Assert.Equal(newBlockSize, blockSize);
+ }
+
+ [Fact]
+ public void StreamingBlockSizeInBytes_ShouldThrowArgumentOutOfRangeException_ForInvalidValue()
+ {
+ // Arrange
+ var options = new EncryptionOptions(KeyWrapAlgorithm.RsaOaep256);
+
+ // Act & Assert
+ Assert.Throws(() => options.StreamingBlockSizeInBytes = 0);
+ Assert.Throws(() => options.StreamingBlockSizeInBytes = -1);
+ }
+
+ [Fact]
+ public void DecryptionKeyName_ShouldBeNullByDefault()
+ {
+ // Arrange
+ var options = new EncryptionOptions(KeyWrapAlgorithm.RsaOaep256);
+
+ // Act
+ var decryptionKeyName = options.DecryptionKeyName;
+
+ // Assert
+ Assert.Null(decryptionKeyName);
+ }
+
+ [Fact]
+ public void EncryptionCipher_ShouldReturnDefaultValue()
+ {
+ // Arrange
+ var options = new EncryptionOptions(KeyWrapAlgorithm.RsaOaep256);
+
+ // Act
+ var encryptionCipher = options.EncryptionCipher;
+
+ // Assert
+ Assert.Equal(DataEncryptionCipher.AesGcm, encryptionCipher); // Default value is AesGcm
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Encryption/Extensions/EncryptionServiceCollectionExtensionsTests.cs b/test/Dapr.Cryptography.Test/Encryption/Extensions/EncryptionServiceCollectionExtensionsTests.cs
new file mode 100644
index 000000000..352f64a34
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Encryption/Extensions/EncryptionServiceCollectionExtensionsTests.cs
@@ -0,0 +1,169 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Dapr.Cryptography.Encryption;
+using Dapr.Cryptography.Encryption.Extensions;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.Cryptography.Test.Encryption.Extensions;
+
+public class EncryptionServiceCollectionExtensionsTests
+{
+ [Fact]
+ public void AddDaprEncryptionClient_FromIConfiguration()
+ {
+ const string apiToken = "acb123";
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary { { "DAPR_API_TOKEN", apiToken } })
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(configuration);
+
+ services.AddDaprEncryptionClient();
+
+ var app = services.BuildServiceProvider();
+
+ var encryptionClient = app.GetRequiredService() as DaprEncryptionGrpcClient;
+
+ Assert.NotNull(encryptionClient!.DaprApiToken);
+ Assert.Equal(apiToken, encryptionClient.DaprApiToken);
+ }
+
+ [Fact]
+ public void AddDaprEncryptionClient_RegistersClientOnlyOnce()
+ {
+ var services = new ServiceCollection();
+
+ var clientBuilder = new Action((sp, builder) =>
+ {
+ builder.UseDaprApiToken("abc");
+ });
+
+ services.AddDaprEncryptionClient(); //Sets a default API token value of an empty string
+ services.AddDaprEncryptionClient(clientBuilder); //Sets the API token value
+
+ var serviceProvider = services.BuildServiceProvider();
+ var daprEncryptionClient = serviceProvider.GetService() as DaprEncryptionGrpcClient;
+
+ Assert.NotNull(daprEncryptionClient!.HttpClient);
+ Assert.False(daprEncryptionClient.HttpClient.DefaultRequestHeaders.TryGetValues("dapr-api-token", out _));
+ }
+
+ [Fact]
+ public void AddDaprEncryptionClient_RegistersIHttpClientFactory()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprEncryptionClient();
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var httpClientFactory = serviceProvider.GetService();
+ Assert.NotNull(httpClientFactory);
+
+ var daprEncryptionClient = serviceProvider.GetService();
+ Assert.NotNull(daprEncryptionClient);
+ }
+
+ [Fact]
+ public void AddDaprEncryptionClient_RegistersUsingDependencyFromIServiceProvider()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ services.AddDaprEncryptionClient((provider, builder) =>
+ {
+ var configProvider = provider.GetRequiredService();
+ var apiToken = configProvider.GetApiTokenValue();
+ builder.UseDaprApiToken(apiToken);
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+ var client = serviceProvider.GetRequiredService() as DaprEncryptionGrpcClient;
+
+ //Validate it's set on the GrpcClient - not that it doesn't get set on the HttpClient
+ Assert.NotNull(client);
+ Assert.NotNull(client.DaprApiToken);
+ Assert.Equal("abcdef", client.DaprApiToken);
+ Assert.NotNull(client.HttpClient);
+
+ if (!client.HttpClient.DefaultRequestHeaders.TryGetValues("dapr-api-token", out var daprApiToken))
+ {
+ Assert.Fail();
+ }
+
+ Assert.Equal("abcdef", daprApiToken.FirstOrDefault());
+ }
+
+ [Fact]
+ public void AddDaprEncryptionClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprEncryptionClient((_, _) => { }, ServiceLifetime.Singleton);
+ var serviceProvider = services.BuildServiceProvider();
+
+ var encryptionClient1 = serviceProvider.GetService();
+ var encryptionClient2 = serviceProvider.GetService();
+
+ Assert.NotNull(encryptionClient1);
+ Assert.NotNull(encryptionClient2);
+
+ Assert.Same(encryptionClient1, encryptionClient2);
+ }
+
+ [Fact]
+ public void AddDaprEncryptionClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprEncryptionClient((_, _) => { }, ServiceLifetime.Transient);
+ var serviceProvider = services.BuildServiceProvider();
+
+ var encryptionClient1 = serviceProvider.GetService();
+ var encryptionClient2 = serviceProvider.GetService();
+
+ Assert.NotNull(encryptionClient1);
+ Assert.NotNull(encryptionClient2);
+ }
+
+ [Fact]
+ public async Task AddDaprEncryptionClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprEncryptionClient((_, _) => { }, ServiceLifetime.Scoped);
+ var serviceProvider = services.BuildServiceProvider();
+
+ await using var scope1 = serviceProvider.CreateAsyncScope();
+ var encryptionClient1 = scope1.ServiceProvider.GetService();
+ Assert.NotNull(encryptionClient1);
+
+ await using var scope2 = serviceProvider.CreateAsyncScope();
+ var encryptionClient2 = scope2.ServiceProvider.GetService();
+ Assert.NotNull(encryptionClient2);
+
+ Assert.NotSame(encryptionClient1, encryptionClient2);
+ }
+
+ private sealed class TestSecretRetriever
+ {
+ public string GetApiTokenValue() => "abcdef";
+ }
+}
diff --git a/test/Dapr.Cryptography.Test/Extensions/AsyncEnumerableExtensionsTests.cs b/test/Dapr.Cryptography.Test/Extensions/AsyncEnumerableExtensionsTests.cs
new file mode 100644
index 000000000..154c1c7a6
--- /dev/null
+++ b/test/Dapr.Cryptography.Test/Extensions/AsyncEnumerableExtensionsTests.cs
@@ -0,0 +1,64 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Threading.Tasks;
+using Dapr.Cryptography.Extensions;
+
+namespace Dapr.Cryptography.Test.Extensions;
+
+public class AsyncEnumerableExtensionsTests
+{
+ [Fact]
+ public async Task Empty_ShouldReturnEmptyAsyncEnumerable()
+ {
+ // Arrange
+ var asyncEnumerable = AsyncEnumerableExtensions.Empty();
+
+ // Act & Assert
+ await foreach (var item in asyncEnumerable)
+ {
+ Assert.Fail("Expected no items in the async enumerable.");
+ }
+ }
+
+ [Fact]
+ public async Task Empty_ShouldReturnEmptyAsyncEnumerable_ForStringType()
+ {
+ // Arrange
+ var asyncEnumerable = AsyncEnumerableExtensions.Empty();
+
+ // Act & Assert
+ await foreach (var item in asyncEnumerable)
+ {
+ Assert.Fail("Expected no items in the async enumerable.");
+ }
+ }
+
+ [Fact]
+ public async Task Empty_ShouldReturnEmptyAsyncEnumerable_ForCustomType()
+ {
+ // Arrange
+ var asyncEnumerable = AsyncEnumerableExtensions.Empty();
+
+ // Act & Assert
+ await foreach (var item in asyncEnumerable)
+ {
+ Assert.Fail("Expected no items in the async enumerable.");
+ }
+ }
+
+ private class MyCustomType
+ {
+ // Custom type definition
+ }
+}