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
<PackageReference Include="Kapela.Security.Encryption.EntityFrameworkCore" Version="10.0.1" />
<PackageVersion Include="Kapela.Security.Encryption.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Kapela.Security.Encryption.EntityFrameworkCore" />
paket add Kapela.Security.Encryption.EntityFrameworkCore --version 10.0.1
#r "nuget: Kapela.Security.Encryption.EntityFrameworkCore, 10.0.1"
#:package Kapela.Security.Encryption.EntityFrameworkCore@10.0.1
#addin nuget:?package=Kapela.Security.Encryption.EntityFrameworkCore&version=10.0.1
#tool nuget:?package=Kapela.Security.Encryption.EntityFrameworkCore&version=10.0.1
🔐 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.csviaEncryptionHelper.SetEncryptionKey, il faut le faire avant l'enregistrement duDbContext. 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
- Ajoutez la colonne au schéma de la base, typée
byte[](varbinary(max)/bytea/BLOB), via votre outil de migration SQL habituel. - Re-scaffoldez vos entités. Les propriétés correspondantes apparaissent typées
byte[]?(par exempleLastNameEncryptedetFirstNameEncrypted). - 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
- Renommez chaque propriété existante en clair en
XxxLegacyet fixez son nom de colonne historique via[Column]. - Ajoutez les nouvelles propriétés
Xxxdé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; }
}
- Générez et appliquez votre migration Entity Framework Core. Elle ajoutera les colonnes
LastNameEncrypted byte[] NULLetFirstNameEncrypted byte[] NULLà côté deLastNameetFirstNamehistoriques. - Dans
Program.cs, aprèsbuilder.Build()et avantapp.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
- Retirez les propriétés
LastNameLegacyetFirstNameLegacydu modèle ainsi que les[Column("LastNameEncrypted")]et[Column("FirstNameEncrypted")]des propriétés cibles. - (Optionnel) Renommez les colonnes
LastNameEncryptedetFirstNameEncryptedenLastNameetFirstNamedans le schéma via une migration EF Core dédiée. - Générez et appliquez la migration EF Core qui supprime la colonne historique.
- 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
- Ajoutez à votre schéma SQL un script qui :
- renomme les colonnes en clair
LastNameetFirstNameenLastNameLegacyetFirstNameLegacy; - ajoute deux nouvelles colonnes
LastNameEncryptedetFirstNameEncryptedtypéesbyte[](varbinary(max)/bytea/BLOB), nullables.
- renomme les colonnes en clair
- Re-scaffoldez vos entités. Vous obtenez désormais quatre propriétés :
LastNameLegacy: string?,LastNameEncrypted: byte[]?,FirstNameLegacy: string?etFirstNameEncrypted: byte[]?. - 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; }
}
- Dans
Program.cs, aprèsbuilder.Build()et avantapp.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
- Script SQL qui supprime les colonnes
LastNameLegacyetFirstNameLegacy. - Re-scaffoldez : seuls
LastNameEncrypted: byte[]?etFirstNameEncrypted: byte[]?subsistent sur l'entité scaffoldée. - Le partial reste inchangé (toujours décoré sur
LastNameEncryptedetFirstNameEncryptedviaSource: nameof(...)). - 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
MigrationResultest retourné (lignes migrées, durée, détails par étape) et des logsInformationsont émis si unILoggerFactoryest 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();
💡
AsDecryptedn'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 | Versions 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. |
-
net10.0
- Kapela.Security.Encryption (>= 10.0.3)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.