tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Using Data Protection in Entity Framework Core with Value Converters

2 Apr 2019 Encryption, Entity Framework Core, Security

When I wrote the post Using value converter for custom encryption of field on Entity Framework Core 2.1 the encryption there was non-existing. My intention was to show the skeleton. But in comments somebody asked how to wire up Data Protection.

Data Protection is a nice feature in .NET Core, that abstracts the converter itself from inventing some half-broken encryption and hence it’s a good idea to use it. The API is pretty straightforward, you get an IDataProtectionProvider and from that you construct IDataProtector which has Protect and Unprotect methods. Let’s wire it up into the Entity Framework’s converter.

public class ProtectedConverter : ValueConverter<string, string>
{
	class Wrapper
	{
		readonly IDataProtector _dataProtector;

		public Wrapper(IDataProtectionProvider dataProtectionProvider)
		{
			_dataProtector = dataProtectionProvider.CreateProtector(nameof(ProtectedConverter));
		}

		public Expression<Func<string, string>> To => x => x != null ? _dataProtector.Protect(x) : null;
		public Expression<Func<string, string>> From => x => x != null ? _dataProtector.Unprotect(x) : null;
	}

	public ProtectedConverter(IDataProtectionProvider provider, ConverterMappingHints mappingHints = default)
		: this(new Wrapper(provider), mappingHints)
	{ }

	ProtectedConverter(Wrapper wrapper, ConverterMappingHints mappingHints)
		: base(wrapper.To, wrapper.From, mappingHints)
	{ }
}

Basically, the same structure as in the previous post. The only trick I had to do (although there’s multiple ways to solve it) is the Wrapper class, which serves as a glue layer to get the expressions into the base constructor.

With that, I can use it in DbContext.

class FooBar
{
	public int Id { get; set; }
	[Protected]
	public string Secret { get; set; }
}

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class ProtectedAttribute : Attribute
{ }

class MyContext : DbContext
{
	readonly IDataProtectionProvider _dataProtectionProvider;

	public MyContext(IDataProtectionProvider dataProtectionProvider)
	{
		_dataProtectionProvider = dataProtectionProvider;
	}

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

		modelBuilder.Entity<FooBar>()
			.Property(x => x.Secret).HasMaxLength(500);

		foreach (var entityType in modelBuilder.Model.GetEntityTypes())
		{
			foreach (var property in entityType.GetProperties())
			{
				var attributes = property.PropertyInfo.GetCustomAttributes(typeof(ProtectedAttribute), false);
				if (attributes.Any())
				{
					property.SetValueConverter(new ProtectedConverter(_dataProtectionProvider));
				}
			}
		}
	}
}

And finally, some basic code to test it.

var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
serviceCollection.AddDbContext<MyContext>();
var services = serviceCollection.BuildServiceProvider();

using (var ctx = ActivatorUtilities.CreateInstance<MyContext>(services))
{
	ctx.Set<FooBar>().Add(new FooBar()
	{
		Secret = "Jiri"
	});
	ctx.SaveChanges();
}

That’s it. Hope it helps.

Profile Picture Jiří Činčura is an independent developer focusing on data and business layers, language constructs, parallelism and databases. Specifically Entity Framework, asynchronous and parallel programming, cloud and Azure. He's Microsoft Most Valuable Professional and you can read his articles, guides, tips and tricks at www.tabsoverspaces.com.