Secure Your Data with Entity Framework Core Encryption (2024)

Abstract

Storing sensitive data in a database is not an easy task. Many things can go wrong, especially if proper precautions aren’t taken. Sensitive information, such as personal details or financial records, can become vulnerable to unauthorized access if encryption and security measures aren’t carefully implemented.

When it comes to encrypting and decrypting data in an application, ASP.NET Core provides a brilliant option for handling encryption with the powerful DataProtection API. Alternatively, you can implement custom encryption logic tailored to your application's requirements. However, the challenge is not only encrypting and decrypting the data but also managing how this encrypted data is stored and retrieved efficiently from the database.

There are multiple ways to handle the storage and retrieval of encrypted data in Entity Framework Core. One common approach is to use backing fields, where encryption and decryption are handled on the fly when the data is accessed via property getters or setters. This way, the sensitive data remains encrypted while at rest in the database but is decrypted just in time when needed by the application.

c#

public record PlayerProfile(ICryptographyService cryptographyService){ private string _email; private string _realName; public string Email { get => cryptographyService.Decrypt(_email); set => _email = cryptographyService.Encrypt(value); } public string RealName { get => cryptographyService.Decrypt(_realName); set => _realName = cryptographyService.Encrypt(value); }}

Another more robust approach leverages Entity Framework Core's ValueConverters, which can be registered at the model creation level. Value converters allow you to specify how data is transformed when being read from or written to the database, making them ideal for encrypting sensitive data at the column level without affecting the rest of the business logic.

In this post, I would like to share a flexible solution based on the second approach to encrypt and decrypt columns in your database.

Implementation

Suppose you have a player profile entity in your application that contains sensitive information, such as a player's real name and email address. To comply with data protection regulations like GDPR, as well as to ensure the privacy of your users, this information must be securely stored in an encrypted format within the database.

c#

public record PlayerProfile{ public required Guid Id { get; init; } public required string Email { get; init; } public required string RealName { get; init; }}

To achieve this, we can implement encryption using a value converter that automatically encrypts the data before it is written to the database and decrypts it when it is retrieved. To perform encryption and decryption, we will use a custom ICryptographyService interface that provides methods for encrypting and decrypting data. For simplicity, we will use a simple reverse encryption algorithm in this example.

c#

public interface ICryptographyService{ string Encrypt(string data); string Decrypt(string data);}public class ReverseCryptographyService : ICryptographyService{ public string Encrypt(string value) { ArgumentNullException.ThrowIfNull(value); var sb = new StringBuilder(); foreach (var c in value.Reverse()) sb.Append(c); return sb.ToString(); } public string Decrypt(string value) { ArgumentNullException.ThrowIfNull(value); var sb = new StringBuilder(); foreach (var c in value.Reverse()) sb.Append(c); return sb.ToString(); }}

Next, we should register our ICryptographyService implementation in the DI container in the Startup class.

c#

var builder = WebApplication.CreateBuilder(args);// ...builder.Services.AddSingleton<ICryptographyService, ReverseCryptographyService>();

Now, we are ready to implement the EncryptedConverter class that will be used to encrypt and decrypt the data. The EncryptedConverter class inherits from the ValueConverter class provided by Entity Framework Core. It takes an instance of the ICryptographyService interface in its constructor and uses it to encrypt and decrypt the data.

c#

public class EncryptedConverter : ValueConverter<string, string>{ public EncryptedConverter(ICryptographyService cryptographyService, ConverterMappingHints? mappingHints = null) : base(v => cryptographyService.Encrypt(v), v => cryptographyService.Decrypt(v), mappingHints) { }}

From now on, we can use fluent configuration or define a new attribute to mark properties that need to be encrypted. Let's start with the first approach.

Fluent configuration

To use the EncryptedConverter class, we need to configure it in the OnModelCreating method of the DbContext class. We can do this by calling the HasConversion method on the PropertyBuilder instance for each property that needs to be encrypted. To keep DbContext clean, we can create a fluent configuration for our PlayerProfile.

c#

public class PlayerProfileConfiguration : IEntityTypeConfiguration<PlayerProfile>{ private readonly ICryptographyService _cryptographyService; public PlayerProfileFluentConfiguration(ICryptographyService cryptographyService) { _cryptographyService = cryptographyService; } public void Configure(EntityTypeBuilder<PlayerProfile> builder) { var converter = new EncryptedConverter(_cryptographyService); builder.HasKey(b => b.Id); builder.Property(b => b.Email) .IsRequired() .HasConversion(converter); builder.Property(b => b.RealName) .IsRequired() .HasConversion(converter); }}

Don't forget to register your configuration in the OnModelCreating method of your DbContext class.

c#

protected override void OnModelCreating(ModelBuilder modelBuilder){ base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new PlayerProfileConfiguration(_cryptographyService));}

Attribute-based configuration

If you prefer to use attributes to mark properties that need to be encrypted, you can create a custom attribute and use it to decorate the properties in your entity class.

c#

[AttributeUsage(AttributeTargets.Property)]public sealed class EncryptedAttribute : Attribute{}

Next step is to add our newly created attribute to the properties that need to be encrypted.

c#

public record PlayerProfile{ [Required] public required Guid Id { get; init; } [Required] [Encrypted] public required string Email { get; init; } [Required] [Encrypted] public required string RealName { get; init; }}

Then we can scan all properties in the OnModelCreating method of the DbContext class and apply the EncryptedConverter to those properties that are marked with the EncryptedAttribute.

c#

protected override void OnModelCreating(ModelBuilder modelBuilder){ base.OnModelCreating(modelBuilder); var stringType = typeof(string); var encryptedAttribute = typeof(EncryptedAttribute); var converter = new EncryptedConverter(_cryptographyService); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { foreach (var property in entityType.GetProperties()) { if (!IsDiscriminator(property) && property.ClrType == stringType && property.PropertyInfo is not null) { var attributes = property.PropertyInfo.GetCustomAttributes(encryptedAttribute, false); if (attributes.Length != 0) { property.SetValueConverter(converter); } } } } bool IsDiscriminator(IMutableProperty property) => property.Name == "Discriminator";}

One gotcha with this approach is ensuring that the scanned property is not a discriminator column. The discriminator column is used by EF Core to store information about the entity type in Table Per Hierarchy (TPH) inheritance scenarios.

Results

Finally, let's see how the conversion of PlayerProfile works!

After inserting a few new records into the database, we can see that the data is stored in an encrypted format.

IdEmailRealName
035de6f3-9c58-4c39-abfe-997f172cef0emoc.elpmaxe@divaDsenoJ divaD
052d8c82-a05f-4590-b444-8590829ab1e1moc.elpmaxe@eilrahCsmailliW eilrahC
2a10c13e-27e2-4a0f-9e87-f1eed8b15354moc.elpmaxe@evEnworB evE
9f1eccaa-5574-411e-8b40-38f362b650aamoc.elpmaxe@ecilAhtimS ecilA
fcfcd03c-d0d1-4bba-ae24-a97b829ee1b8moc.elpmaxe@boBnosnhoJ boB

When we retrieve the data from the database, the encrypted values should be automatically decrypted. Let's test this scenario by querying and listing items in our store.

plaintext

info: Encrypto.DemoService[0] Profile: Id: 520f79c9-a16e-47ce-9ede-6aeeac857ac4 Email: [emailprotected] Name: Eve Browninfo: Encrypto.DemoService[0] Profile: Id: 669e4809-0bcb-4523-8bc9-54c7fec8b752 Email: [emailprotected] Name: Bob Johnsoninfo: Encrypto.DemoService[0] Profile: Id: 8b68946a-b490-47df-a9f6-fcc7c481de6a Email: [emailprotected] Name: Charlie Williamsinfo: Encrypto.DemoService[0] Profile: Id: 8c7f41a7-bb6f-4297-b17f-612a6e8881a4 Email: [emailprotected] Name: Alice Smithinfo: Encrypto.DemoService[0] Profile: Id: f21dcba3-40e1-4fa4-9239-e8bbf2532928 Email: [emailprotected] Name: David Jones

Conclusion

Securing sensitive data is crucial for both user privacy and legal compliance. By utilizing value converters in Entity Framework Core, you can efficiently encrypt and decrypt data stored in your database columns with minimal overhead.

Whether you use the built-in DataProtection API or implement your own encryption service, these two approaches are providing a flexible and powerful solution for handling encrypted data at scale.

Further reading

  1. Entity Framework Core Value Conversions
  2. ASP.NET Data Protection

Last modified:

For commercial reprinting, please contact the webmaster for authorization, for non-commercial reprinting, please indicate the source of this article and the link to the article, you are free to copy and distribute the work in any media and in any form, and you can also modify and create, but the same license agreement must be used when distributing derivative works. This article is licensed under the CC BY-NC-SA 4.0 license.

Enabling Hot Reload in a Jekyll Container on Windows

Secure Your Data with Entity Framework Core Encryption (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Madonna Wisozk

Last Updated:

Views: 5943

Rating: 4.8 / 5 (48 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Madonna Wisozk

Birthday: 2001-02-23

Address: 656 Gerhold Summit, Sidneyberg, FL 78179-2512

Phone: +6742282696652

Job: Customer Banking Liaison

Hobby: Flower arranging, Yo-yoing, Tai chi, Rowing, Macrame, Urban exploration, Knife making

Introduction: My name is Madonna Wisozk, I am a attractive, healthy, thoughtful, faithful, open, vivacious, zany person who loves writing and wants to share my knowledge and understanding with you.