EfCore.EncryptedProperties 1.0.0

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

EfCore.EncryptedProperties

Property-level encryption for Entity Framework Core 9. Mark the properties that should be protected, configure where keys live, and keep using your entities in a normal EF workflow.

  • Targets: .NET 9, EF Core 9
  • Use it for: PII, notes, tokens, small secrets, and values the database should never see in plaintext
  • Entity experience: normal CLR properties for transparent reads, or EncryptedValue<T> when you want explicit async decryption

Install

dotnet add package EfCore.EncryptedProperties
<PackageReference Include="EfCore.EncryptedProperties" Version="1.0.0" />

Quick Start

Register encryption services once in application DI, then enable the EF integration on each encrypted DbContext.

using EfCore.EncryptedProperties.Extensions;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;

services.AddEncryptedProperties(cfg => cfg
    .WithFileRsaKeyProvider("rsa-key.pem", "rsa-v1")
    .WithDatabaseKeyChain(SqlClientFactory.Instance, connectionString)
    .WithKeyChainPreloadOnStartup());

services.AddDbContext<AppDbContext>((sp, options) =>
{
    options
        .UseSqlServer(connectionString)
        .UseEncryptedProperties(sp);
});

If you use the database key chain, add its table to your model:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.UseEncryptedPropertiesKekStorage();

    modelBuilder.Entity<Customer>(entity =>
    {
        entity.HasKey(e => e.Id);
        entity.Property(e => e.Email).IsEncrypted();
        entity.Property(e => e.SecretNotes).IsEncrypted(opts => opts.KeyPurpose = "notes");
    });
}

Then use the entity normally:

var customer = new Customer
{
    Name = "Alice",
    Email = "alice@example.com",
    SecretNotes = "private message"
};

db.Customers.Add(customer);
await db.SaveChangesAsync();

var saved = await db.Customers.FindAsync(customer.Id);
Console.WriteLine(saved!.Email);
Console.WriteLine(await saved.SecretNotes.GetDecryptedValueAsync());

Entity Styles

Choose the style by choosing the CLR type.

Transparent Reads

Use the real property type when you want the value decrypted as soon as EF materializes the entity.

public sealed class Customer
{
    public Guid Id { get; set; }
    public string Email { get; set; } = string.Empty;
}

This is the easiest option for everyday fields like email, phone number, or a short identifier.

Explicit Async Reads

Use EncryptedValue<T> when you want to defer decryption until application code asks for the value.

public sealed class Customer
{
    public Guid Id { get; set; }
    public EncryptedValue<string> SecretNotes { get; set; } = default!;
}

customer.SecretNotes = "private message";

var notes = await customer.SecretNotes.GetDecryptedValueAsync(ct);

This is useful for larger values, values rarely shown to users, or code paths where you want decryption to be obvious.

Setup Recipes

Tests and Local Demos

services.AddEncryptedProperties(cfg => cfg
    .WithInMemoryRsaKeyProvider(RSA.Create(2048), "test-rsa-v1")
    .WithInMemoryKeyChain());

In-memory keys are lost when the process exits. They are for tests, demos, and short-lived local runs.

Self-hosted Apps

services.AddEncryptedProperties(cfg => cfg
    .WithFileRsaKeyProvider("keys/rsa-key.pem", "rsa-v1")
    .WithDatabaseKeyChain(SqlClientFactory.Instance, connectionString));

The file provider creates the PEM file if it does not exist. Back it up and protect it like any other application secret.

Azure Key Vault

services.AddEncryptedProperties(cfg => cfg
    .WithAzureKeyVaultRsaKeyProvider(
        new Uri("https://my-vault.vault.azure.net/keys/my-key"),
        new DefaultAzureCredential())
    .WithDatabaseKeyChain(SqlClientFactory.Instance, connectionString));

Use this when the RSA private key should stay outside the application host. Pass the unversioned Key Vault key URI; new KEKs use the latest key version, while existing KEKs store and use the exact versioned Key Vault key ID that wrapped them.

Keep old Key Vault key versions enabled and recoverable while any KEKs wrapped by those versions still exist.

Key Rotation

services.AddEncryptedProperties(cfg => cfg
    .WithFileRsaKeyProvider("rsa-key.pem", "rsa-v1")
    .WithDatabaseKeyChain(SqlClientFactory.Instance, connectionString)
    .WithKeyChainRotation(policy =>
    {
        policy.KeyRotateAfter = TimeSpan.FromDays(90);
    }));

New writes use the current active key for the property's purpose. Existing rows remain readable after rotation.

Startup KEK Preload

services.AddEncryptedProperties(cfg => cfg
    .WithFileRsaKeyProvider("rsa-key.pem", "rsa-v1")
    .WithDatabaseKeyChain(SqlClientFactory.Instance, connectionString)
    .WithKeyChainPreloadOnStartup());

This registers an IHostedService that unwraps all stored KEKs during host startup. If preload fails, the app fails fast instead of discovering key access problems on the first encrypted read or write.

What To Expect

  • The database column stores ciphertext, not the original value.
  • SaveChanges encrypts new or changed encrypted properties.
  • Materialization decrypts transparent properties automatically.
  • EncryptedValue<T> decrypts only when GetDecryptedValueAsync is called, then caches the plaintext in that wrapper instance.
  • Different key purposes rotate independently, so Email and SecretNotes can use separate key chains.

Supported value types are primitives, string, byte[], bool, DateTime, DateTimeOffset, Guid, enums backed by supported primitives, and nullable variants.

Edge Cases

Queries

Do not query encrypted columns by plaintext:

// This will not work reliably.
var customer = await db.Customers.SingleOrDefaultAsync(c => c.Email == "alice@example.com");

Ciphertext changes on each write, even for the same plaintext. For lookups, keep a separate non-encrypted lookup column such as a normalized hash.

Migrations

If you use WithDatabaseKeyChain, call modelBuilder.UseEncryptedPropertiesKekStorage() and create the table with migrations or EnsureCreated().

Encrypted entity properties are still mapped to normal database columns, but those columns hold ciphertext.

The key-chain table enforces one active KEK per purpose with a filtered unique index on Purpose where IsActive = 1.

Nulls and Defaults

null encrypted reference or nullable values are stored as null. For non-nullable value types, a missing encrypted payload materializes as the CLR default value.

Keys

Keep the RSA key stable. If the file key is deleted, replaced, or a different Key Vault key is configured, previously stored key-chain records may no longer unwrap.

The library rotates data-encryption keys, but it does not automatically rotate the RSA master key.

Plaintext Change Tracking

For transparent properties, assign the new value and call SaveChanges as usual. For EncryptedValue<T>, assigning from T marks the wrapper as modified:

customer.SecretNotes = "updated private message";
await db.SaveChangesAsync();

Plaintext Is Still In Your Process

This protects data from being stored in plaintext in the database. It does not hide values from your application code, logs, memory dumps, or API responses. Treat decrypted values carefully once you read them.

Samples

License

Apache License, Version 2.0.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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 was computed.  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
1.0.5 46 5/27/2026
1.0.4 110 5/20/2026
1.0.3 107 5/20/2026
1.0.2 97 5/17/2026
1.0.0 94 5/16/2026

Initial public release with transparent encrypted properties, explicit async encrypted values, file/in-memory/Azure Key Vault RSA providers, database/in-memory key chains, and key rotation support.