tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Using Data Protection in Entity Framework Core with Value Converters

2 Apr 2019 2 mins 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 .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.