2021-11-12

Using Data Protection in Entity Framework Core with Value Converters (v1.1)

This post is intended as an addendum on Mgr. Jiří Činčura's excellent post Using Data Protection in Entity Framework Core with Value Converters (archive 1, archive 2). In it, he explains how you can use an Entity Framework Core ValueConverter to encrypt data before it being stored in the database and decrypt it when reading from the database.

Though his post is excellent I do have one minor nitpick with it: you can't use (a) purpose string(s) with it; this means all encryption happens with the same 'purpose'. To explain what this 'purpose' is about I'll quote the docs:

Components which consume IDataProtectionProvider must pass a unique purposes parameter to the CreateProtector method. The purposes parameter is inherent to the security of the data protection system, as it provides isolation between cryptographic consumers, even if the root cryptographic keys are the same.

When a consumer specifies a purpose, the purpose string is used along with the root cryptographic keys to derive cryptographic subkeys unique to that consumer. This isolates the consumer from all other cryptographic consumers in the application: no other component can read its payloads, and it cannot read any other component's payloads. This isolation also renders infeasible entire categories of attack against the component.

So it's a good idea to have a purpose string and even better to be able to specify it. Luckily, the solution is rather simple (and elegant) and only a minimal few changes need to be made.

First, the Protected attribute; we'll extend it to have a Purposes property and a constructor overload so you'll be able to specify one (or more) purpose(s):

Next, the OnModelCreated method is changed a little:

And that's it!

So what this does is: whenever you simply use a [Protected] attribute the typename of the entity is used as 'purpose'. As the comment points out, this is considered a 'good rule of thumb' as stated in the docs:

Using the namespace and type name of the component consuming the data protection APIs is a good rule of thumb, as in practice this information will never conflict.

However, because the ProtectedAttribute class now has an overload that takes any number of strings we can simply specify one or more purposes directly at the entity level.

For completeness' sake, here's the ProtectedValueConverter, should the referenced blog (and/or archived versions) ever go offline, which I have modified slightly too:

Oh, another little change I made is that this is now nullable aware 😊

And there you have it, a way to specify purpose(s) on "protected" attributes. With credit to Mgr. Jiří Činčura for providing the foundation for this work.

No comments:

Post a Comment