Kapela.Security.Encryption.EntityFrameworkCore 10.0.1

Prefix Reserved
dotnet add package Kapela.Security.Encryption.EntityFrameworkCore --version 10.0.1
                    
NuGet\Install-Package Kapela.Security.Encryption.EntityFrameworkCore -Version 10.0.1
                    
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="Kapela.Security.Encryption.EntityFrameworkCore" Version="10.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kapela.Security.Encryption.EntityFrameworkCore" Version="10.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Kapela.Security.Encryption.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 Kapela.Security.Encryption.EntityFrameworkCore --version 10.0.1
                    
#r "nuget: Kapela.Security.Encryption.EntityFrameworkCore, 10.0.1"
                    
#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 Kapela.Security.Encryption.EntityFrameworkCore@10.0.1
                    
#: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=Kapela.Security.Encryption.EntityFrameworkCore&version=10.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Kapela.Security.Encryption.EntityFrameworkCore&version=10.0.1
                    
Install as a Cake Tool

🔐 Kapela.Security.Encryption.EntityFrameworkCore

Extension Entity Framework Core de Kapela.Security.Encryption : chiffrez de manière transparente les propriétés sensibles de vos entités, sans modifier votre code applicatif.


📦 Installation

dotnet add package Kapela.Security.Encryption.EntityFrameworkCore

Le package dépend de Kapela.Security.Encryption (chiffrement AES-GCM authentifié) et de Microsoft.EntityFrameworkCore.Relational (compatible SQL Server, MariaDB / MySQL, PostgreSQL, SQLite).


🗝️ Clé de chiffrement

Toutes les options d'initialisation de la clé de chiffrement sont décrites dans la documentation de Kapela.Security.Encryption.

⚠️ Spécifique à Entity Framework Core : si vous initialisez la clé directement dans Program.cs via EncryptionHelper.SetEncryptionKey, il faut le faire avant l'enregistrement du DbContext. Le contexte d'encryption est figé au build du modèle Entity Framework Core.

Le passage d'un EncryptionContext explicite à UseKapelaEncryption(context) est également supporté pour les scénarios multi-tenant ou de migration de clé.


🆕 Mise en place sur une nouvelle colonne

Cas où vous ajoutez une colonne chiffrée à une entité, sans avoir à migrer de données existantes en clair.

⚙️ Code-First

Décorez la propriété avec [EncryptedColumn]. La colonne sous-jacente est mappée automatiquement en byte[] (varbinary / bytea / BLOB) par la convention.

public class User
{
    public int Id { get; set; }
    public string Username { get; set; } = "";

    [EncryptedColumn]
    public string LastName { get; set; } = "";

    [EncryptedColumn]
    public string FirstName { get; set; } = "";
}

Générez et appliquez votre migration Entity Framework Core comme d'habitude (dotnet ef migrations add AddUserEncryptedFields puis dotnet ef database update). Les colonnes seront créées en byte[] côté provider.

⚙️ DB-First

  1. Ajoutez la colonne au schéma de la base, typée byte[] (varbinary(max) / bytea / BLOB), via votre outil de migration SQL habituel.
  2. Re-scaffoldez vos entités. Les propriétés correspondantes apparaissent typées byte[]? (par exemple LastNameEncrypted et FirstNameEncrypted).
  3. Ajoutez un fichier partial qui expose les valeurs en clair via des propriétés décorées par [EncryptedColumn], en pointant vers les propriétés scaffoldées :
// User.Partial.cs (à côté de User.cs scaffoldé)
public partial class User
{
    [EncryptedColumn(Source: nameof(LastNameEncrypted))]
    public string? LastName { get; set; }

    [EncryptedColumn(Source: nameof(FirstNameEncrypted))]
    public string? FirstName { get; set; }
}

Au build du modèle, la convention retire les propriétés scaffoldées LastNameEncrypted et FirstNameEncrypted du modèle EF Core, redirige LastName et FirstName vers leurs colonnes respectives et y applique le convertisseur de chiffrement. Côté code applicatif, vous manipulez exclusivement User.LastName et User.FirstName en clair.

✏️ À l'usage

Quel que soit le mode choisi (Code-First ou DB-First), la manipulation du code est identique :

db.Users.Add(new User { Username = "adupont", LastName = "Dupont", FirstName = "Alice" });
db.SaveChanges();
// LastName et FirstName sont chiffrés à l'insertion ; Username reste en clair.

var user = db.Users.Single(u => u.Username == "adupont");
// user.LastName == "Dupont", user.FirstName == "Alice". Déchiffrés à la lecture.

🔄 Migration d'une colonne existante en clair

Cas où une colonne contient déjà des données en clair et que vous souhaitez les chiffrer en place. La procédure se fait en deux déploiements : le premier introduit la colonne chiffrée et y migre les données, le second retire l'ancienne colonne en clair.

⚙️ Code-First

Déploiement 1 — ajout de la colonne chiffrée et migration des données
  1. Renommez chaque propriété existante en clair en XxxLegacy et fixez son nom de colonne historique via [Column].
  2. Ajoutez les nouvelles propriétés Xxx décorées par [EncryptedColumn], pointant chacune vers une colonne dédiée.
public class User
{
    public int Id { get; set; }
    public string Username { get; set; } = "";

    [Column("LastName")]                           // colonne historique en clair
    public string? LastNameLegacy { get; set; }

    [Column("LastNameEncrypted")]                  // nouvelle colonne dédiée
    [EncryptedColumn]
    public string? LastName { get; set; }

    [Column("FirstName")]                        // colonne historique en clair
    public string? FirstNameLegacy { get; set; }

    [Column("FirstNameEncrypted")]               // nouvelle colonne dédiée
    [EncryptedColumn]
    public string? FirstName { get; set; }
}
  1. Générez et appliquez votre migration Entity Framework Core. Elle ajoutera les colonnes LastNameEncrypted byte[] NULL et FirstNameEncrypted byte[] NULL à côté de LastName et FirstName historiques.
  2. Dans Program.cs, après builder.Build() et avant app.Run(), déclenchez la migration des données :
var app = builder.Build();

await app.Services.RunKapelaEncryptionMigrationAsync<MyDbContext>(m => m
    .Encrypt((User u) => u.LastNameLegacy, u => u.LastName)
    .Encrypt((User u) => u.FirstNameLegacy, u => u.FirstName));

app.Run();

À ce stade, chaque ligne ayant des valeurs dans LastNameLegacy et FirstNameLegacy voit ses équivalents chiffrés écrits dans LastNameEncrypted et FirstNameEncrypted. Les données en clair sont toujours présentes, prêtes pour un éventuel rollback.

Déploiement 2 — retrait de la colonne en clair
  1. Retirez les propriétés LastNameLegacy et FirstNameLegacy du modèle ainsi que les [Column("LastNameEncrypted")] et [Column("FirstNameEncrypted")] des propriétés cibles.
  2. (Optionnel) Renommez les colonnes LastNameEncrypted et FirstNameEncrypted en LastName et FirstName dans le schéma via une migration EF Core dédiée.
  3. Générez et appliquez la migration EF Core qui supprime la colonne historique.
  4. Retirez l'appel à RunKapelaEncryptionMigrationAsync (il n'a plus rien à migrer).

⚙️ DB-First

Déploiement 1 — ajout de la colonne chiffrée et migration des données
  1. Ajoutez à votre schéma SQL un script qui :
    • renomme les colonnes en clair LastName et FirstName en LastNameLegacy et FirstNameLegacy ;
    • ajoute deux nouvelles colonnes LastNameEncrypted et FirstNameEncrypted typées byte[] (varbinary(max) / bytea / BLOB), nullables.
  2. Re-scaffoldez vos entités. Vous obtenez désormais quatre propriétés : LastNameLegacy: string?, LastNameEncrypted: byte[]?, FirstNameLegacy: string? et FirstNameEncrypted: byte[]?.
  3. Ajoutez un fichier partial qui expose les valeurs en clair via des propriétés décorées :
public partial class User
{
    [EncryptedColumn(Source: nameof(LastNameEncrypted))]
    public string? LastName { get; set; }

    [EncryptedColumn(Source: nameof(FirstNameEncrypted))]
    public string? FirstName { get; set; }
}
  1. Dans Program.cs, après builder.Build() et avant app.Run() :
await app.Services.RunKapelaEncryptionMigrationAsync<MyDbContext>(m => m
    .Encrypt((User u) => u.LastNameLegacy, u => u.LastName)
    .Encrypt((User u) => u.FirstNameLegacy, u => u.FirstName));

À ce stade, User.LastName et User.FirstName (en clair, exposés par le partial) sont remplis pour toutes les lignes ayant des valeurs dans LastNameLegacy et FirstNameLegacy, et les colonnes LastNameEncrypted et FirstNameEncrypted contiennent les payloads chiffrés correspondants.

Déploiement 2 — retrait de la colonne en clair
  1. Script SQL qui supprime les colonnes LastNameLegacy et FirstNameLegacy.
  2. Re-scaffoldez : seuls LastNameEncrypted: byte[]? et FirstNameEncrypted: byte[]? subsistent sur l'entité scaffoldée.
  3. Le partial reste inchangé (toujours décoré sur LastNameEncrypted et FirstNameEncrypted via Source: nameof(...)).
  4. Retirez l'appel à RunKapelaEncryptionMigrationAsync.

Caractéristiques du runner de migration

  • Idempotente : seules les lignes dont la propriété cible n'est pas encore renseignée sont migrées. Une seconde exécution sur un état déjà migré n'a aucun effet.
  • Chunked : traitement par lots de 1 000 lignes avec SaveChangesAsync à chaque lot.
  • Fail-fast : la première erreur stoppe l'exécution et remonte l'exception ; les lots déjà commités restent intacts. Après correction, relancez : la migration reprend là où elle s'est arrêtée.
  • Observable : un MigrationResult est retourné (lignes migrées, durée, détails par étape) et des logs Information sont émis si un ILoggerFactory est disponible dans le container DI.

💱 Types CLR pris en charge

string, bool, char, types entiers (byte, sbyte, short, ushort, int, uint, long, ulong), types flottants (float, double), decimal, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, Guid, enum, byte[]. Les versions nullables sont gérées nativement par Entity Framework Core.

Le format de sérialisation interne est invariant (culture, format ISO 8601, etc.) et stable : il ne change pas entre versions mineures du package, garantissant la lisibilité long terme des données chiffrées.

Mode de stockage des enum

Par défaut, les valeurs enum sont sérialisées par nom de membre ("Active"), ce qui les rend robustes à toute renumérotation. Pour préférer la valeur sous-jacente ("1"), passez EnumStorage :

[EncryptedColumn(EnumStorage: EnumStorageMode.ByValue)]
public Status Status { get; set; }

Cas particulier de byte[]

Le contenu binaire est encodé en base64 avant chiffrement, ce qui ajoute environ 33 % de surcoût de stockage. Pour des volumes importants (plusieurs Mo par ligne), envisagez plutôt un stockage blob côté infrastructure plutôt que ce package.


🚧 Contraintes Entity Framework incompatibles

Le chiffrement n'est pas déterministe (un IV aléatoire est utilisé à chaque écriture, ce qui est une garantie de sécurité). En conséquence, une propriété marquée [EncryptedColumn] ne peut pas participer à :

  • une clé primaire (PK)
  • une clé alternative (AK)
  • un index (unique ou non)
  • une clé étrangère (FK)

Toute violation est détectée au démarrage du modèle et lève une InvalidOperationException agrégée listant l'ensemble des problèmes :

Une ou plusieurs propriétés [EncryptedColumn] sont incompatibles avec
un chiffrement non-déterministe :
  - User.Email : Index unique 'IX_Users_Email'
  - Order.Notes : Clé étrangère 'FK_Orders_Notes_NotesTable'

Plus largement, toute opération SQL qui inspecte la valeur de la colonne (égalité, LIKE, ORDER BY, GROUP BY, agrégat, jointure) ne fonctionne pas comme attendu sur une propriété chiffrée. Pour ces cas, voir la section ci-dessous.


🔍 Requêtes et filtrage côté client

Une fois qu'une propriété est chiffrée, elle ne peut plus être filtrée côté SQL. La méthode d'extension AsDecrypted() rend explicite la transition vers un traitement en mémoire dans votre code de requête :

var alphaUsers = db.Users
    .Where(u => u.IsActive)             // côté serveur, sur une propriété non chiffrée
    .AsDecrypted()                       // bascule en énumération côté client
    .Where(u => u.Email.EndsWith("@kapela.fr"))  // côté client, sur la valeur déchiffrée
    .ToList();

💡 AsDecrypted n'effectue aucun déchiffrement supplémentaire — celui-ci est déjà pris en charge par le convertisseur lors de la matérialisation. Le helper sert uniquement à clarifier l'intention.

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
10.0.1 98 5/11/2026
10.0.0 93 5/11/2026