Davasorus.Utility.DotNet.Encryption 2026.2.2.7

dotnet add package Davasorus.Utility.DotNet.Encryption --version 2026.2.2.7
                    
NuGet\Install-Package Davasorus.Utility.DotNet.Encryption -Version 2026.2.2.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Davasorus.Utility.DotNet.Encryption" Version="2026.2.2.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Davasorus.Utility.DotNet.Encryption" Version="2026.2.2.7" />
                    
Directory.Packages.props
<PackageReference Include="Davasorus.Utility.DotNet.Encryption" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Davasorus.Utility.DotNet.Encryption --version 2026.2.2.7
                    
#r "nuget: Davasorus.Utility.DotNet.Encryption, 2026.2.2.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Davasorus.Utility.DotNet.Encryption@2026.2.2.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Davasorus.Utility.DotNet.Encryption&version=2026.2.2.7
                    
Install as a Cake Addin
#tool nuget:?package=Davasorus.Utility.DotNet.Encryption&version=2026.2.2.7
                    
Install as a Cake Tool

Davasorus.Utility.DotNet.Encryption

⚠️ Security Notice

V1 and V2 are retained for backward compatibility with existing ciphertext. Both use unauthenticated AES-CBC with fixed key-derivation parameters and should be considered obfuscation, not encryption, for new code. Use V3 for any new encryption needs.

V3 uses AES-256-GCM with a random IV per message, an authenticated envelope, and caller-supplied key material — no hardcoded secrets, no network dependencies.

Overview

Davasorus.Utility.DotNet.Encryption provides encryption and decryption utilities for .NET applications. The package ships three generations side by side:

  • V1 — legacy sync API, AES-CBC, PBKDF2-HMAC-SHA1 ([Obsolete], retained for backward compatibility).
  • V2 — async API, AES-CBC, PBKDF2-HMAC-SHA512 ([Obsolete], retained for backward compatibility).
  • V3 — AES-256-GCM, authenticated envelope, random IV per message, caller-supplied keys, string/byte[]/Stream overloads. This is the recommended API for new code.

Features

  • V3: AES-256-GCM authenticated encryption, random IV per message, embedded key-name for rotation
  • V3: string, byte[] / ReadOnlyMemory<byte>, and Stream overloads
  • V3: DecryptionResult for expected-failure outcomes; throws only on programmer errors
  • V3: zero-on-dispose Client buffers (best-effort in-memory exposure mitigation)
  • V1/V2: legacy AES-CBC with PBKDF2, retained for wire compatibility
  • .NET 8 compatible
  • OpenTelemetry traces + metrics for all three generations

V3 Quickstart

using Microsoft.Extensions.DependencyInjection;
using Davasorus.Utility.DotNet.Encryption.V3.Configuration;
using Davasorus.Utility.DotNet.Encryption.V3.Service;

// At startup:
var key = Convert.FromBase64String(builder.Configuration["Encryption:Keys:Primary"]!); // 32 bytes
builder.Services.AddEncryptionServicesV3(opts => opts.AddKey("primary", key));

// In a scoped handler:
public class Handler(IEncryptionServiceV3 enc, IDecryptionServiceV3 dec)
{
    public async Task<string> WrapAsync(string secret, CancellationToken ct)
        => await enc.EncryptAsync(secret, "primary", ct);

    public async Task<string?> UnwrapAsync(string cipher, CancellationToken ct)
    {
        var result = await dec.DecryptAsync(cipher, ct);
        return result.Success ? result.Value : null;
    }
}

Lifecycles are fixed (not configurable): IEncryptionServiceV3/IDecryptionServiceV3 are Scoped, IEncryptionClientV3/IDecryptionClientV3 are Transient. The Service resolves a fresh Client per call and disposes it immediately — the Client zeros its key/plaintext buffers on dispose as a best-effort in-memory exposure mitigation.

V3 Envelope Format

Each V3 ciphertext is a base64url-encoded envelope:

Offset Size Field
0 1 byte Version (0x01)
1 1 byte Key-name length N
2 N bytes Key name (ASCII, [A-Za-z0-9._-], 1..64 chars)
2+N 12 bytes Random IV
14+N L bytes AES-GCM ciphertext
14+N+L 16 bytes GCM auth tag

The key name is embedded so that decrypt can look up the right key from the registered map. This enables key rotation: register the new key under a new name, start writing with it, and old data continues to decrypt as long as the old key is still registered.

V3 Key Rotation

builder.Services.AddEncryptionServicesV3(opts =>
{
    opts.AddKey("2026-Q1", oldKeyBytes);  // still decrypts old data
    opts.AddKey("2026-Q2", newKeyBytes);  // new data is encrypted under this
});

// App code chooses "2026-Q2" for new writes:
var cipher = await enc.EncryptAsync(value, "2026-Q2");

// Decrypt picks the right key based on the envelope:
var result = await dec.DecryptAsync(cipher);  // works for both 2026-Q1 and 2026-Q2 ciphertext

When you're confident no 2026-Q1 ciphertext remains in the wild, remove it from the registration.

V3 Error Model

  • Throws ArgumentNullException, EncryptionKeyNotFoundException, ObjectDisposedException, OperationCanceledException — for programmer errors and cancellation.
  • Returns DecryptionResult with Success = false and a DecryptionError for expected-failure decrypt outcomes: InvalidEnvelope, CiphertextTooShort, UnsupportedVersion, UnknownKey, AuthenticationFailed.

Encrypt returns a plain string or byte[] on success (no result-wrapper — encrypt has no expected-failure mode once inputs validate).

Known Limitations

  • V3 Stream overloads are buffered, not streaming. AES-GCM's auth tag is computed over the whole message, so the Stream overloads read the full input into memory before encrypting. For payloads larger than tens of megabytes, split or encrypt at the record level.
  • Zero-on-dispose is best-effort. V3 Clients zero their local key/plaintext buffers when disposed, but GC compaction may have copied the buffer in managed memory before we zero it. True secret-in-memory safety requires OS-level locked memory, which this package does not provide.
  • No forward secrecy. AES-GCM is symmetric — compromise of a key compromises all ciphertext ever produced under that key.
  • No replay protection. Ciphertext is a value; same plaintext re-encrypted produces different ciphertext, but the original ciphertext can be replayed. Applications requiring replay protection must handle it at the application layer (timestamps, nonces).
  • Offline only. V3 does not integrate with KMS, Key Vault, or any remote key provider. Keys must be supplied locally at DI registration.

OpenTelemetry Integration

This package includes comprehensive OpenTelemetry instrumentation for distributed tracing, metrics, and observability across all encryption and decryption operations (V1, V2, and V3).

Telemetry Data Emitted

Activities (Spans):

  • Encryption.EncryptValue - Service-level encryption operations (V2)
  • Encryption.Encrypt - Client-level encryption operations (V2)
  • Encryption.V1.Encrypt - Legacy encryption operations (V1)
  • Decryption.DecryptValue - Service-level decryption operations (V2)
  • Decryption.Decrypt - Client-level decryption operations (V2)
  • Decryption.V1.Decrypt - Legacy decryption operations (V1)

Tags (Attributes):

  • encryption.operation / decryption.operation - Operation type (encrypt/decrypt)
  • encryption.input_length / decryption.input_length - Input data length in characters
  • encryption.output_length / decryption.output_length - Output data length in characters
  • encryption.valid_usage / decryption.valid_usage - Whether the usage type is valid
  • encryption.success / decryption.success - Whether the operation succeeded
  • encryption.version / decryption.version - API version (v1 for legacy APIs)
  • crypto.algorithm - Encryption algorithm used (AES)
  • crypto.key_derivation - Key derivation function (PBKDF2-HMAC-SHA512 for V2, PBKDF2-HMAC-SHA1 for V1)
  • crypto.iterations - PBKDF2 iteration count (10,000)

Note: The encryption.usage / decryption.usage tags were removed. Because the Usage enum values (Static, Dynamic, api, web, desktop) are effectively the passwords used for V1/V2 PBKDF2 derivation, emitting them as trace tags would broadcast the secret. V3 has no such concern — keys are supplied at DI registration, and only the key name is exposed in traces.

Migrator (Davasorus.Utility.Encryption.V3.Migration source): one span per MigrateAsync call, named Encryption.V3.Migrate. Tags: migration.source_requested, migration.source_resolved, migration.target_key, migration.input_length, migration.output_length, error.type (failures only). See the Migrating legacy V1/V2 ciphertext to V3 section below for full details and the migration.target_key PII note.

Metrics

Three instruments are emitted (via the shared Telemetry package's MeterHelper):

Instrument Kind Unit Dimensions
encryption.operations.total Counter {operation} version ∈ {v1,v2,v3}, operation ∈ {encrypt,decrypt}, success
encryption.duration Histogram ms same dimensions
encryption.input_bytes Histogram bytes same dimensions

Events:

  • encoding.completed - Unicode encoding phase completed (encryption) with bytes_length
  • decoding.completed - Base64 decoding phase completed (decryption) with bytes_length
  • crypto.started - Cryptographic operation started
  • crypto.completed - Cryptographic operation completed
  • exception - Exception occurred with full exception details (type, message, stacktrace)

Configuring Telemetry

The package uses Davasorus.Utility.Encryption as the ActivitySource name. To enable telemetry collection, configure OpenTelemetry in your application startup:

using OpenTelemetry.Trace;

// In Program.cs or Startup.cs
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("Davasorus.Utility.Encryption") // Enable encryption telemetry
        .AddAspNetCoreInstrumentation()        // Optional: ASP.NET Core tracing
        .AddHttpClientInstrumentation()        // Optional: HTTP client tracing
        .AddConsoleExporter()                  // For development
        // OR for production:
        .AddOtlpExporter(options =>
        {
            options.Endpoint = new Uri("http://your-otel-collector:4317");
        })
    );

Trace Context Integration

All encryption and decryption operations automatically include trace context (TraceId, SpanId, ParentSpanId) in structured log messages. This enables seamless correlation between:

  • Distributed traces across microservices
  • Application logs
  • Performance metrics
  • Error tracking

Example log output with trace context:

{
  "timestamp": "2025-01-15T10:30:45.123Z",
  "level": "Information",
  "message": "Encryption complete",
  "TraceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "SpanId": "00f067aa0ba902b7",
  "ParentSpanId": "b7ad6b7169203331",
  "InputLength": 256
}

Performance Analysis

The granular events enable detailed performance analysis:

  1. Encoding Phase - Time spent converting data to bytes

    • Encryption: Unicode encoding (encoding.completed event)
    • Decryption: Base64 decoding (decoding.completed event)
  2. Cryptographic Phase - Time spent in AES operations

    • Between crypto.started and crypto.completed events
    • Includes key derivation (PBKDF2) and encryption/decryption

Use these events in your APM tool to identify bottlenecks and optimize performance.

Security Considerations

No sensitive data is exposed in telemetry:

  • ✅ Only metadata is logged (lengths, usage types, algorithms)
  • ❌ Plaintext values are never included in traces
  • ❌ Encrypted values are never included in traces
  • ❌ Encryption keys are never included in traces
  • ❌ Salts are never included in traces

Exception messages are captured for debugging, but they do not contain sensitive cryptographic material.

Example: Full Trace

Span: Encryption.EncryptValue [Service]
├─ Tags: encryption.operation=encrypt, encryption.input_length=256
├─ Child Span: Encryption.Encrypt [Client]
│  ├─ Tags: crypto.algorithm=AES, crypto.key_derivation=PBKDF2-HMAC-SHA512, crypto.iterations=10000
│  ├─ Event: encoding.completed (bytes_length=512)
│  ├─ Event: crypto.started
│  ├─ Event: crypto.completed
│  └─ Tags: encryption.output_length=344, encryption.success=true
└─ Status: Ok

Migrating legacy V1/V2 ciphertext to V3

If you have data encrypted under V1 or V2 that needs to be re-encrypted under V3, the package ships an IEncryptionMigrator (auto-registered by AddEncryptionServicesV3) that converts legacy ciphertext to V3 ciphertext without exposing plaintext to the caller. Plaintext exists only transiently inside the migrator while it decrypts the legacy value and immediately re-encrypts it as V3.

Quick example

services.AddEncryptionServicesV3(b =>
    b.AddKey("rotated-2026", keyBytes).SetActiveKey("rotated-2026"));

// Inside a scoped service:
public class ConfigMigration(IEncryptionMigrator migrator)
{
    public async Task<string> RewrapAsync(string legacyCiphertext, string legacyUsage)
    {
        var result = await migrator.MigrateAsync(legacyCiphertext, legacyUsage);
        if (!result.Success)
        {
            throw new InvalidOperationException(
                $"Migration failed: {result.Error} — {result.ErrorMessage}");
        }
        return result.Value!;
    }
}

Overload matrix

The migrator exposes eight MigrateAsync overloads, organized along three axes:

Axis Choices
Input shape Loose string (legacyCiphertext, legacyUsage) OR UtilitySettingsModel
Source version Auto-detect (none specified) OR explicit LegacyVersion.V1 / V2
Target V3 key Active (uses SetActiveKey) OR explicit targetKeyName

Two × two × two = eight overloads. Pick the one that matches your call site's available context.

Auto-detect: deterministic, not heuristic

When you don't pass a LegacyVersion, the migrator dispatches based on the legacy Usage value:

  • "api", "web", "desktop"LegacyVersion.V1
  • "Static", "Dynamic"LegacyVersion.V2
  • Any other value → MigrationResult.Fail(InvalidLegacyUsage, ...)

V1 and V2 Usage sets are disjoint, so dispatch is deterministic. There is no probing fallback — if Usage doesn't match either set, the call fails fast with InvalidLegacyUsage. Use the explicit-version overloads when you already know the source version (one less Usage validation roundtrip).

SetActiveKey setup

The parameterless-target overloads need a configured active key:

services.AddEncryptionServicesV3(b =>
    b.AddKey("rotated-2026", keyBytes)
     .SetActiveKey("rotated-2026"));     // required for auto-target overloads

SetActiveKey validates at registration: if the named key wasn't added, registration throws InvalidOperationException.

Recommendation for batch migrations: prefer the explicit-key overloads. If a batch job runs while another tenant rotates the active key mid-flight, in-flight migrations could otherwise re-route to the new key unexpectedly. Explicit targetKeyName makes the rotation target visible at the call site.

Errors

MigrationResult.Error (MigrationError):

Code When
InvalidLegacyUsage Legacy Usage isn't in V1's {api, web, desktop} or V2's {Static, Dynamic}.
LegacyDecryptFailed V1/V2 returned empty (malformed ciphertext, wrong key, truncation).
TargetKeyNotFound Explicit-key overload referenced an unregistered V3 key.
NoActiveKeyConfigured Active-key overload but no SetActiveKey call.
AmbiguousLegacyFormat Reserved for future use. Not currently produced.

Programmer errors (null arguments, cancellation) throw ArgumentNullException / OperationCanceledException — they don't surface via MigrationResult.

Plaintext exposure caveat

V1 and V2 work in string plaintext, which the runtime can't securely zero (string is immutable, GC-managed). The migrator's plaintext-exposure window is the same as any caller using V1/V2 directly today — i.e., the plaintext lives in a heap-allocated string for the duration of the V1/V2 decrypt + V3 encrypt cycle, then becomes GC-eligible. V3's per-call client zeros its own scratch buffers via Dispose, so the V3 portion of the path is buffer-zeroed; the V1/V2 portion is not.

For workloads where this matters, consider re-encrypting from a controlled source (e.g., re-issue from an offline KMS) rather than reading from existing storage.

Worked example: migrate one DB row in place

public sealed class CredentialsRotator(
    IConfigurationStore store,
    IEncryptionMigrator migrator)
{
    public async Task<bool> RewrapRowAsync(int rowId, CancellationToken ct = default)
    {
        var row = await store.LoadAsync(rowId, ct);
        var result = await migrator.MigrateAsync(row.EncryptedValue, row.Usage, ct);
        if (!result.Success)
        {
            // Surface to caller without exposing plaintext.
            return false;
        }
        await store.UpdateAsync(rowId, encryptedValue: result.Value!, ct);
        return true;
    }
}

The plaintext is never visible to RewrapRowAsync. The migrator's input and output are both encrypted strings.

Telemetry

Each migrate call emits an Activity span on the Davasorus.Utility.Encryption.V3.Migration ActivitySource and a metric datapoint on the existing Davasorus.Utility.Encryption.Operations meter (counter encryption.operations.total).

Span tags include migration.source_requested (v1 / v2 / auto), migration.source_resolved (v1 / v2, set when dispatch succeeds), migration.target_key, migration.input_length, and migration.output_length (success only). Failed calls additionally tag error.type with the MigrationError value.

migration.target_key PII note. This tag emits the V3 key name on every migrate span. Most key names are fine to expose to a trace backend (Jaeger, Tempo, etc.). If your key naming scheme embeds tenant identifiers, customer IDs, or other PII (e.g., tenant-12345-2026-q1), either rename your keys to opaque labels or filter the tag at your collector before export. The package emits the literal key name; sanitization is the consumer's responsibility.

Metric tags use version=migration and operation=v1->v3 / v2->v3 / auto->v3. The operation value reflects the caller's choice of overload, not the resolved version (so you can see "what overloads are consumers calling" in dashboards).

Getting Started

  1. Reference the library in your .NET project.
  2. Register the services and clients with your DI container (see below).
  3. Only interact with the service interfaces (IEncryptionServiceV3/IDecryptionServiceV3 for new code, or IEncryptionService/IDecryptionService for V2) in your application code. The service manages all communication with the client internally.

Note: Do not use the client classes directly in your application code. The service layer encapsulates all client logic, error handling, and logging.

Usage Example

V3 (Recommended for new code): see the V3 Quickstart above.

V1 (Synchronous, legacy):

var encrypt = new Encrypt(logger);
var encrypted = encrypt.EncryptValue(model);
var decrypt = new Decrypt(logger);
var decrypted = decrypt.DecryptValue(model);

V2 (Asynchronous, legacy):

// Register services and clients in DI (see below) 

// In your consuming code, inject IEncryptionService and IDecryptionService
public class MyClass
{
    private readonly IEncryptionService _encryptionService;
    private readonly IDecryptionService _decryptionService;

    public MyClass(IEncryptionService encryptionService, IDecryptionService decryptionService)
    {
        _encryptionService = encryptionService;
        _decryptionService = decryptionService;
    }

    public async Task<string> EncryptAndDecrypt(UtilitySettingsModel model)
    {
        var encrypted = await _encryptionService.EncryptValue(model);
        var decrypted = await _decryptionService.DecryptValue(model);
        return decrypted;
    }
}

API Overview

  • V3 (recommended)
    • IEncryptionServiceV3.EncryptAsync(string plaintext, string keyName, CancellationToken): Task<string> Encrypts a UTF-8 string and returns the base64url-encoded envelope.

    • IEncryptionServiceV3.EncryptAsync(ReadOnlyMemory<byte> plaintext, string keyName, CancellationToken): Task<byte[]> Encrypts raw bytes and returns the raw envelope bytes.

    • IEncryptionServiceV3.EncryptAsync(Stream input, Stream output, string keyName, CancellationToken): Task Reads bytes from input and writes the envelope to output. Buffered, not streaming — see Known Limitations.

    • IDecryptionServiceV3.DecryptAsync(string ciphertext, CancellationToken): Task<DecryptionResult> Decrypts a base64url-encoded envelope. Returns DecryptionResult with Success/Value/Error.

    • IDecryptionServiceV3.DecryptAsync(ReadOnlyMemory<byte> ciphertext, CancellationToken): Task<DecryptionResult<byte[]>> Decrypts raw envelope bytes.

    • IDecryptionServiceV3.DecryptAsync(Stream input, Stream output, CancellationToken): Task<DecryptionResult<long>> Decrypts envelope bytes from input into output; the result Value is the number of plaintext bytes written.

    • Key registration is by name at DI time via EncryptionV3OptionsBuilder.AddKey(string name, byte[] key). Keys must be 32 bytes (AES-256). Names are ASCII [A-Za-z0-9._-], 1..64 chars, and embedded in each envelope so decrypt can route to the right key.

    • Error model: see V3 Error Model above. Decrypt returns DecryptionResult for expected failures; encrypt throws only on programmer errors.

  • V1
    • EncryptValue(UtilitySettingsModel model): string
      Synchronously encrypts the provided UtilitySettingsModel and returns the encrypted string. This method is part of the legacy V1 API and is intended for use in synchronous workflows.

    • DecryptValue(UtilitySettingsModel model): string
      Synchronously decrypts the provided UtilitySettingsModel and returns the decrypted string. Use this method when asynchronous processing is not required.

    • Usage enum:

      • api
        For API-based scenarios where encryption/decryption is performed in service endpoints.
      • web
        For web application scenarios, such as encrypting data in web forms or cookies.
      • desktop
        For desktop application scenarios, such as encrypting configuration files or user data.
  • V2
    • EncryptValueAsync(UtilitySettingsModel model): Task<string>
      Asynchronously encrypts the provided UtilitySettingsModel. This method is internal to the service and not intended for direct use.

    • DecryptValueAsync(UtilitySettingsModel model): Task<string>
      Asynchronously decrypts the provided UtilitySettingsModel. This method is internal to the service and not intended for direct use.

    • Service API:

      • EncryptValue(UtilitySettingsModel model): Task<string>
        Public asynchronous method to encrypt a UtilitySettingsModel via the service interface.
      • DecryptValue(UtilitySettingsModel model): Task<string>
        Public asynchronous method to decrypt a UtilitySettingsModel via the service interface.
    • Usage enum:

      • Static
        For scenarios where encryption parameters remain constant.
      • Dynamic
        For scenarios where encryption parameters may change per operation.

Service/Client Pattern

  • Do not use the client interfaces (IEncryptionClient/IDecryptionClient for V2, IEncryptionClientV3/IDecryptionClientV3 for V3) directly.
  • Always use the service interfaces in your application code: IEncryptionServiceV3/IDecryptionServiceV3 for new code, or IEncryptionService/IDecryptionService for V2.
  • The service handles all error handling, logging, and client interaction. For V3, the Scoped service resolves a fresh Transient client per call and disposes it immediately so the client's key/plaintext buffers can be zeroed.

Key Differences: V1 vs V2 vs V3

Feature/Behavior V1 (Legacy) V2 (Legacy) V3 (Recommended)
API Design Synchronous Asynchronous Asynchronous, with string/byte[]/Stream overloads
Algorithm AES-CBC (unauthenticated) AES-CBC (unauthenticated) AES-256-GCM (authenticated)
IV / Nonce Derived from password Derived from password Random 12-byte IV per message
Key Material Hardcoded Usage enum value Hardcoded Usage enum value Caller-supplied 32-byte keys, registered by name in DI
Key Derivation PBKDF2-HMAC-SHA1, static salt PBKDF2-HMAC-SHA512, named salt None — keys are used directly
Key Rotation Not supported Not supported Supported via key-name embedded in envelope
Error Handling Returns empty string on error Returns empty string, logs, throws on invalid usage Returns DecryptionResult for expected failures; throws only on programmer errors
.NET Support .NET 8 .NET 8 .NET 8
Status [Obsolete] [Obsolete] Recommended for new code

Summary of Improvements in V3

  • Authenticated encryption (AES-GCM) — tampering with ciphertext is detected, not silently decrypted to garbage
  • Random IV per message — same plaintext encrypted twice produces different ciphertext
  • Caller-supplied keys — no hardcoded password material, no Usage enum
  • Key rotation built in — key name is embedded in the envelope, multiple keys can be registered
  • string / byte[] / Stream overloads on both services
  • Explicit error model: DecryptionResult distinguishes expected failures (bad envelope, unknown key, auth failure) from programmer errors (null args, disposed objects)
  • Best-effort zero-on-dispose for in-memory key/plaintext buffers

Dependency Injection (DI) Usage

Use AddEncryptionServicesV3 from Davasorus.Utility.DotNet.Encryption.V3.Configuration. At least one key must be registered or registration throws InvalidOperationException. Lifecycles are fixed: Service is Scoped, Client is Transient — these are part of the in-memory-exposure mitigation and are not configurable.

using Davasorus.Utility.DotNet.Encryption.V3.Configuration;

services.AddEncryptionServicesV3(opts =>
{
    opts.AddKey("primary", primaryKeyBytes);   // 32-byte AES-256 key
    opts.AddKey("2026-Q1", oldKeyBytes);       // optional: additional keys for rotation
});

V3 registration is independent of V2 — registering V3 does not modify V2 wiring, and the two can coexist during migration.

V2 (Legacy)

V2 uses the built-in extension methods from Davasorus.Utility.DotNet.Encryption.Configuration:

Recommended: Extension methods (registers both encryption and decryption)

using Davasorus.Utility.DotNet.Encryption.Configuration;

// Default (Scoped lifetime)
services.AddEncryptionServices();

// With custom lifetime
services.AddEncryptionServices(config => config.UseTransientLifetime());
services.AddEncryptionServices(config => config.UseSingletonLifetime());

Register only encryption or decryption:

services.AddEncryptionOnly();
services.AddDecryptionOnly();

Manual registration (alternative):

services.AddScoped<IEncryptionService, EncryptionService>();
services.AddScoped<IEncryptionClient, EncryptionClient>();
services.AddScoped<IDecryptionService, DecryptionService>();
services.AddScoped<IDecryptionClient, DecryptionClient>();

Note: Only inject and use the service interfaces (IEncryptionServiceV3/IDecryptionServiceV3 for V3, or IEncryptionService/IDecryptionService for V2) in your application code. The service manages all client interactions.

Example Test Cases

  • V3: encrypt/decrypt round-trip across string, byte[], and Stream overloads
  • V3: key rotation — ciphertext written under one registered key still decrypts after a second key is added
  • V3: tampered envelope (flipped byte in ciphertext or auth tag) returns DecryptionResult { Success = false, Error = AuthenticationFailed }
  • V3: unknown key name in envelope returns Error = UnknownKey; truncated/garbled envelope returns InvalidEnvelope / CiphertextTooShort / UnsupportedVersion
  • V3: programmer errors (null inputs, disposed client, missing key at encrypt time) throw rather than returning a result
  • V1/V2: encrypt and decrypt with all supported usage types
  • V1/V2: handle invalid usage gracefully (returns empty string)
  • V1/V2: log errors on exceptions
  • Async tests for V2 and V3

Dependencies

License

MIT License

Contributing

Contributions are welcome! Please submit issues or pull requests for improvements.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Davasorus.Utility.DotNet.Encryption:

Package Downloads
Davasorus.Utility.DotNet.SQS

Amazon SQS interaction for TEPS Utilities

Davasorus.Utility.DotNet.Auth

Handles Authentication for TEPS Utilities

Davasorus.Utility.DotNet.Api

API Interaction for TEPS Utilities with generic deserialization, configurable error reporting, and improved DI configuration. Supports REST, GraphQL, gRPC, WebSocket, SignalR, and SSE protocols.

SA.OpenSearchTool.Business

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.2.2.7 79 5/5/2026
2026.2.2.6 85 5/5/2026
2026.2.2.5 70 5/5/2026
2026.2.2.4 81 5/5/2026
2026.2.2.3 71 5/5/2026
2026.2.2.2 490 5/1/2026
2026.2.2.1 144 5/1/2026
2026.2.1.7 274 4/23/2026
2026.2.1.6 116 4/23/2026
2026.2.1.5 122 4/22/2026
2026.2.1.4 167 4/16/2026
2026.2.1.3 3,378 4/9/2026
2026.2.1.2 112 4/9/2026
2026.2.1.1 1,354 4/1/2026
2026.1.3.5 749 3/29/2026
2026.1.3.4 640 3/24/2026
2026.1.3.3 2,031 3/12/2026
2026.1.3.2 137 3/12/2026
2026.1.3.1 639 3/10/2026
2026.1.2.6 1,747 2/17/2026
Loading failed