EntityFrameworkCore.Crypto.DataEncryption
10.2.0
See the version list below for details.
dotnet add package EntityFrameworkCore.Crypto.DataEncryption --version 10.2.0
NuGet\Install-Package EntityFrameworkCore.Crypto.DataEncryption -Version 10.2.0
<PackageReference Include="EntityFrameworkCore.Crypto.DataEncryption" Version="10.2.0" />
<PackageVersion Include="EntityFrameworkCore.Crypto.DataEncryption" Version="10.2.0" />
<PackageReference Include="EntityFrameworkCore.Crypto.DataEncryption" />
paket add EntityFrameworkCore.Crypto.DataEncryption --version 10.2.0
#r "nuget: EntityFrameworkCore.Crypto.DataEncryption, 10.2.0"
#:package EntityFrameworkCore.Crypto.DataEncryption@10.2.0
#addin nuget:?package=EntityFrameworkCore.Crypto.DataEncryption&version=10.2.0
#tool nuget:?package=EntityFrameworkCore.Crypto.DataEncryption&version=10.2.0
EntityFrameworkCore.Crypto.DataEncryption
Transparent field-level encryption for Entity Framework Core 8, 9 and 10. Data is encrypted in your application process before it reaches the database, so the database, its administrators and its backups only ever see ciphertext. Keys never leave your process.
Website • Complete guide • Roadmap • Security
Install
dotnet add package EntityFrameworkCore.Crypto.DataEncryption
Quick start
Mark the properties to encrypt, then turn encryption on in OnModelCreating:
public class Customer
{
public int Id { get; set; }
[CryptoEncrypted]
public string Ssn { get; set; } = string.Empty;
[CryptoEncrypted]
public string Email { get; set; } = string.Empty;
[CryptoEncrypted]
[Column(TypeName = "TEXT")]
public decimal AnnualSalary { get; set; }
}
public class AppDbContext : DbContext
{
private readonly IEncryptionCryptoProvider _provider = new AesGcmCryptoProvider(LoadKeyFromSecureStore()); // 32-byte key
public DbSet<Customer> Customers => Set<Customer>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.UseEncryption(_provider); // call last
}
EF Core encrypts on SaveChanges and decrypts when rows are read. Supported types: string, byte[], long, decimal, double, Dictionary<string, string>, JSON classes and owned entity types.
What you get
| Capability | Notes |
|---|---|
| AES-256-GCM authenticated encryption | Random nonce per value, tamper detection, key IDs and key rings for rotation. |
| Cloud KMS | Azure Key Vault, AWS KMS, HashiCorp Vault. |
| Searchable columns | Blind index for equality lookups (HasBlindIndex, WhereBlindEquals). |
| Substring search and sorting | Exact Contains / StartsWith / EndsWith and in-application sorting. The key is never sent to the database. |
| Query guard | Queries that cannot work on ciphertext throw instead of silently returning wrong results. |
| Batch re-encryption | Verified, resumable AES-CBC to AES-GCM migration and key rotation (EncryptionRotator). |
| Legacy migration bridge | MigrationCryptoProvider reads old AES-CBC data and writes AES-GCM. |
| Auto-encryption | UseAutoEncryption encrypts sensitive columns by naming convention. |
| Observability | Health checks, OpenTelemetry metrics. |
| Scanning, monitoring and compliance | PII scanner with ML classifier, Crypto Sentinel anomaly detection, email/webhook alerts, decryption audit trail, DPDP / GDPR compliance reports. |
Searching encrypted columns
// Equality: a keyed hash in an indexed shadow column
modelBuilder.Entity<Customer>().Property(c => c.Email).HasBlindIndex();
var customer = await context.Customers.WhereBlindEquals(nameof(Customer.Email), email, hasher).FirstOrDefaultAsync();
var customers = await context.Customers.WhereBlindIn(context, nameof(Customer.Email), emails, hasher).ToListAsync(); // IN list
// Substring search and sorting
modelBuilder.Entity<Customer>().Property(c => c.Ssn).HasSearchIndex();
var rows = await context.Customers.AsEncrypted(context, hasher)
.WhereContains(c => c.Ssn, "123")
.OrderBy(c => c.Ssn)
.ToListAsync(maxRows: 5_000);
Results are exact: the index narrows candidates in SQL and each one is confirmed against the decrypted value. Sorting runs in the application and needs a maxRows cap, so it throws instead of truncating. Contains needs a term of at least 3 characters. Existing rows become searchable after a one-time SearchIndexBackfill.
Measured on 300,000 rows (single runs, default Docker containers on one desktop, no tuning, no PostgreSQL trigram index):
| PostgreSQL 16 | SQL Server 2022 | MySQL 8.4 | |
|---|---|---|---|
Contains("marnia") → 69 rows |
77 ms | 111 ms | 358 ms |
StartsWith("MARIA") → 1,169 rows |
53 ms | 158 ms | 305 ms |
| SQL filter + sort, 30,000 rows | 168 ms | 468 ms | 495 ms |
Tested on all three databases and SQLite: results match the plaintext answer exactly (including %, _, accents and mixed case), sort order with nulls, upgrade of legacy CBC data, CBC to GCM and key rotation, and no plaintext or keys in the PostgreSQL and MySQL server logs. Not measured: concurrent load and tables over 300,000 rows.
Setup, limits and method: search and re-encryption guide. Everything else, including KMS, scanning, alerts and compliance: full reference.
Support
| Product | Versions 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 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 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. |
-
net10.0
- MailKit (>= 4.18.2)
- Microsoft.EntityFrameworkCore (>= 10.0.14)
- Microsoft.Extensions.Caching.Memory (>= 10.0.14)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Http (>= 10.0.14)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.14)
- Microsoft.ML (>= 5.0.1)
- Microsoft.ML.TimeSeries (>= 5.0.1)
-
net8.0
- MailKit (>= 4.18.2)
- Microsoft.EntityFrameworkCore (>= 8.0.33)
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Options.DataAnnotations (>= 8.0.0)
- Microsoft.ML (>= 5.0.1)
- Microsoft.ML.TimeSeries (>= 5.0.1)
-
net9.0
- MailKit (>= 4.18.2)
- Microsoft.EntityFrameworkCore (>= 9.0.22)
- Microsoft.Extensions.Caching.Memory (>= 9.0.22)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.22)
- Microsoft.Extensions.Http (>= 9.0.22)
- Microsoft.Extensions.Options.DataAnnotations (>= 9.0.22)
- Microsoft.ML (>= 5.0.1)
- Microsoft.ML.TimeSeries (>= 5.0.1)
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 | |
|---|---|---|---|
| 10.6.5 | 162 | 10/5/2026 | |
| 10.6.0 | 242 | 9/10/2026 | |
| 10.5.0 | 145 | 9/8/2026 | |
| 10.3.0 | 0 | 11/5/2026 | |
| 10.2.0 | 0 | 11/5/2026 | |
| 10.1.0 | 0 | 11/5/2026 | |
| 10.0.1 | 4,889 | 11/12/2025 | |
| 10.0.0 | 4,697 | 11/12/2025 | |
| 9.0.0 | 1,015 | 6/23/2025 | |
| 8.0.3 | 69,384 | 5/29/2024 | |
| 8.0.2 | 456 | 5/27/2024 | |
| 8.0.1 | 430 | 5/27/2024 | |
| 8.0.0 | 453 | 5/24/2024 |
Version 10.2.0:
- New: exact Contains / StartsWith / EndsWith search on encrypted columns through a keyed search index (HasSearchIndex, AsEncrypted). The key is never sent to the database.
- New: in-application sorting of encrypted columns with a chosen culture and null placement. A required maxRows cap makes it throw instead of truncating.
- New: query guard (UseEncryptedQueryGuard). Queries that cannot work on ciphertext throw EncryptedQueryNotSupportedException instead of silently returning zero rows or a scrambled order.
- New: per-column normalization (WithNormalization), SearchIndexBackfill for existing rows, and EncryptionRotator for verified, resumable batch re-encryption (CBC to GCM, key rotation).
- Fixed: MigrationCryptoProvider could fail to read about 1 in 256 legacy AES-CBC values whose first bytes looked like a GCM envelope with an unregistered key ID.
- Tested on SQLite, PostgreSQL 16, MySQL 8.4 and SQL Server 2022. See docs/SEARCH_AND_REENCRYPTION.md for limits and measured performance.
Roadmap: https://github.com/efcore-encryption/EntityFrameworkCore.Crypto.DataEncryption/blob/main/ROADMAP.md