Wiaoj.Security.EntityFrameworkCore 0.0.1-alpha.61

This is a prerelease version of Wiaoj.Security.EntityFrameworkCore.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Wiaoj.Security.EntityFrameworkCore --version 0.0.1-alpha.61
                    
NuGet\Install-Package Wiaoj.Security.EntityFrameworkCore -Version 0.0.1-alpha.61
                    
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="Wiaoj.Security.EntityFrameworkCore" Version="0.0.1-alpha.61" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Wiaoj.Security.EntityFrameworkCore" Version="0.0.1-alpha.61" />
                    
Directory.Packages.props
<PackageReference Include="Wiaoj.Security.EntityFrameworkCore" />
                    
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 Wiaoj.Security.EntityFrameworkCore --version 0.0.1-alpha.61
                    
#r "nuget: Wiaoj.Security.EntityFrameworkCore, 0.0.1-alpha.61"
                    
#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 Wiaoj.Security.EntityFrameworkCore@0.0.1-alpha.61
                    
#: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=Wiaoj.Security.EntityFrameworkCore&version=0.0.1-alpha.61&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Wiaoj.Security.EntityFrameworkCore&version=0.0.1-alpha.61&prerelease
                    
Install as a Cake Tool

Wiaoj.Security — Automatic Key Rotation

Architecture

App Startup
    │
    ▼
KeyRingLoader<TContext>
    │  Loads EncryptionKeyRecord rows from DB
    │  Unwraps each key with MasterKeyWrapper (AES-GCM)
    │  → Builds KeyRing<TContext>
    │
    ▼
ManagedSecretProtector<TContext>   ← singleton, hot-reloadable
    │  Wraps SecretProtector<TContext>
    │  Injected as ISecretProtector<TContext>
    │
    │  (every CheckInterval — default 6h)
    ▼
RotationBackgroundService<TContext>
    │  Creates scoped KeyRotationService<TContext>
    │
    ▼
KeyRotationService<TContext>
    ├─ RotateIfNeededAsync()   ← checks key age vs RotationInterval
    └─ ForceRotateAsync()      ← manual endpoint / admin trigger
         │
         ├─ 1. Generate new AES key
         ├─ 2. Wrap with master key → save to DB
         ├─ 3. Retire old key in DB
         ├─ 4. ManagedSecretProtector.ReloadAsync() → atomic swap
         └─ 5. IDataRotator<TContext>.RotateBatchAsync() (if registered)

⚠️ AES Key Sizes

AES supports 128 / 192 / 256-bit keys only. There is no AES-512.

Size Security Notes
128-bit ~128-bit classical Acceptable for low-sensitivity data
192-bit ~192-bit classical Rarely used
256-bit ~256-bit classical Default. NSA Suite B, recommended.

Configure via KeySizeInBits (see Setup below).


Setup

1. Apply EF Core migration

Implement IEncryptionKeyDbContext on your DbContext:

public class AppDbContext : DbContext, IEncryptionKeyDbContext
{
    public DbSet<EncryptionKeyRecord> EncryptionKeys { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new EncryptionKeyRecordConfiguration());
        // ... your other configs
    }
}

Then add and apply a migration:

dotnet ef migrations add AddEncryptionKeys
dotnet ef database update

2. Generate a master key

# bash
openssl rand -base64 32

# PowerShell
[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))

# C#
Console.WriteLine(Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)));

Store it in your secrets manager, NOT in source control or appsettings.json.

3. Register services

// Program.cs
builder.Services
    // Master key source (swap for Azure Key Vault / AWS KMS in production)
    .AddEnvironmentMasterKeyProvider("APP_MASTER_KEY")

    // Register the full rotation system for each context
    .AddManagedSecretProtector<WebhookSigningContext, AppDbContext>(opts =>
    {
        opts.RotationInterval      = TimeSpan.FromDays(90); // change key every 90 days
        opts.CheckInterval         = TimeSpan.FromHours(6); // wake up every 6h to check
        opts.KeySizeInBits         = 256;                   // 128, 192, or 256
        opts.AutoRotateData        = true;                  // re-encrypt data after key rotation
        opts.DataRotationBatchSize = 100;
    })

    // Wire up your IDataRotator implementation
    .AddDataRotator<WebhookSigningContext, WebhookDataRotator>();

4. Define your secret domains

// Empty marker classes — enforced by the type system at compile time
public sealed class WebhookSigningContext   : ISecretContext { }
public sealed class PaymentGatewayContext   : ISecretContext { }
public sealed class OAuthClientSecretContext : ISecretContext { }

5. Implement IDataRotator

public sealed class WebhookDataRotator : IDataRotator<WebhookSigningContext>
{
    private readonly AppDbContext _db;
    private readonly ISecretProtector<WebhookSigningContext> _protector;

    public WebhookDataRotator(AppDbContext db, ISecretProtector<WebhookSigningContext> protector)
    {
        _db        = db;
        _protector = protector;
    }

    public async Task<int> RotateBatchAsync(int batchSize, CancellationToken ct)
    {
        var stale = await _db.Webhooks
            .Where(w => w.SecretKeyVersion < (int)_protector.CurrentKeyVersion)
            .Take(batchSize)
            .ToListAsync(ct);

        foreach (var webhook in stale)
        {
            var encrypted = EncryptedSecret<WebhookSigningContext>.FromPersisted(
                webhook.EncryptedSecret, webhook.SecretKeyVersion);

            var rotated = _protector.Rotate(encrypted);

            webhook.EncryptedSecret  = rotated.Blob.ToStorageString();
            webhook.SecretKeyVersion = rotated.KeyVersion.Value;
        }

        await _db.SaveChangesAsync(ct);
        return stale.Count;
    }

    public async Task<bool> IsCompleteAsync(CancellationToken ct)
        => !await _db.Webhooks.AnyAsync(
            w => w.SecretKeyVersion < (int)_protector.CurrentKeyVersion, ct);
}

6. Manual / admin-triggered rotation

// Controller or minimal API endpoint
app.MapPost("/admin/keys/rotate/{context}", async (
    string context,
    KeyRotationService<WebhookSigningContext> svc,
    CancellationToken ct) =>
{
    await svc.ForceRotateAsync(ct);
    return Results.Ok(new { rotated = true });
}).RequireAuthorization("Admin");

Custom Master Key Provider (production)

// Azure Key Vault example
public sealed class AzureKeyVaultMasterKeyProvider : IMasterKeyProvider
{
    private readonly SecretClient _client;
    private readonly string _secretName;

    public AzureKeyVaultMasterKeyProvider(SecretClient client, string secretName)
    {
        _client     = client;
        _secretName = secretName;
    }

    public async ValueTask<Secret<byte>> GetMasterKeyAsync(CancellationToken ct = default)
    {
        KeyVaultSecret secret = await _client.GetSecretAsync(_secretName, cancellationToken: ct);
        byte[] keyBytes = Convert.FromBase64String(secret.Value);
        try
        {
            return Secret<byte>.From(keyBytes.AsSpan());
        }
        finally
        {
            CryptographicOperations.ZeroMemory(keyBytes);
        }
    }
}

// Registration
builder.Services.AddMasterKeyProvider<AzureKeyVaultMasterKeyProvider>();

Key lifecycle diagram

Day 0:    Key v1 created  (active)
Day 90:   Key v2 created  (active), v1 retired (can still decrypt old data)
Day 180:  Key v3 created  (active), v2 retired
          Once all v1 data is re-encrypted, v1 can be deleted from DB

          Old retired keys stay in the DB until ALL data referencing them
          has been re-encrypted. After that they are safe to remove.

What goes in the DB

Column Type Contents
context_name varchar(256) E.g. "WebhookSigningContext"
version int 1, 2, 3, …
wrapped_key_material varchar(1024) Base64(nonce|tag|ciphertext) — encrypted with master key
created_at datetime UTC creation time
retired_at datetime? UTC retirement time (null = still active)

The master key never touches the database.

Product Compatible and additional computed target framework versions.
.NET 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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1-alpha.80 90 6/17/2026
0.0.1-alpha.79 54 6/17/2026
0.0.1-alpha.78 55 6/17/2026
0.0.1-alpha.77 56 6/17/2026
0.0.1-alpha.76 58 6/17/2026
0.0.1-alpha.75 59 6/11/2026
0.0.1-alpha.74 61 6/10/2026
0.0.1-alpha.73 57 6/5/2026
0.0.1-alpha.72 62 6/4/2026
0.0.1-alpha.71 57 6/2/2026
0.0.1-alpha.70 57 5/30/2026
0.0.1-alpha.69 55 5/15/2026
0.0.1-alpha.68 51 5/15/2026
0.0.1-alpha.67 56 5/14/2026
0.0.1-alpha.66 52 5/13/2026
0.0.1-alpha.65 61 5/12/2026
0.0.1-alpha.64 51 5/12/2026
0.0.1-alpha.63 60 5/12/2026
0.0.1-alpha.62 58 5/8/2026
0.0.1-alpha.61 48 5/6/2026
Loading failed