Storing passwords in a database with the IDataProtector in ASP.NET Core 1.0

What and why?

In one of the projects I’m currently working on, I wanted to protect data that is stored in a database.
In this case it is a password, required to access an external API, that I would not want to save as plain text in the database.
As this password is user specific, it cannot be easily done any different via e.g. userSecrets.

The ASP.NET Core application, currently using version 1.0.0-rc2, can handle this out of the box.
https://docs.asp.net/en/1.0.0-rc2/security/data-protection/introduction.html

Web applications often need to store security-sensitive data. Windows provides DPAPI for desktop applications but this is unsuitable for web applications. The ASP.NET Core data protection stack provide a simple, easy to use cryptographic API a developer can use to protect data, including key management and rotation.

The ASP.NET Core data protection stack is designed to serve as the long-term replacement for the element in ASP.NET 1.x – 4.x. It was designed to address many of the shortcomings of the old cryptographic stack while providing an out-of-the-box solution for the majority of use cases modern applications are likely to encounter.

In the database this results to some nice protected password that we can Unprotect when needed

2016-06-25 18_20_28

The used code snippets

I used a base controller that receives the IDataProtectionProvider from the class that inherits it. An example class can look like:

public class AccountController : BaseController
{
    public AccountController(
        DataContext dataContext,
        IDataProtectionProvider dataProtectionProvider,
        UserManager<User> userManager
        ) : base(dataContext, dataProtectionProvider, userManager)
    {
    }
}

The base controller looks like:

public abstract class BaseController : Controller
{
    protected DataContext DataContext { get; set; }
    protected IDataProtector DataProtector { get; set; }
    protected UserManager<User> UserManager { get; set; }

    protected StartpageController(
        DataContext dataContext, 
        IDataProtectionProvider dataProtectionProvider,
        UserManager<User> userManager)
    {
        DataContext = dataContext;
        const string name = nameof (StartpageController);
        DataProtector = dataProtectionProvider.CreateProtector(name);
        UserManager = userManager;
    }

    protected User LoggedInUser
    {
        get
        {
            var userId = UserManager.GetUserId(User);
            var user = DataContext.Users.Single(a_item => a_item.Id == userId);
            user.JiraPasswordDecrypted = DataProtector.Unprotect(user.JiraPasswordEncrypted);
            return user;
        }
    } 
}

In my Startup.cs the following options are needed in order for the DataProtector to work.

public void ConfigureServices(IServiceCollection services)
{
    var certificatesDirectory = new DirectoryInfo(@"SomeAwesomeSharedLocation\Certificates"));
    services.AddDataProtection(options => options.ApplicationDiscriminator = "StartpageCore")
        .SetApplicationName("StartpageCore")
        .PersistKeysToFileSystem(certificatesDirectory);
}

I have set the ApplicationDiscriminator in order to use the keys troughout a webfarm environment.

Unfortunately I cannot set the DataProtection with the option ProtectKeysWithCertificate. This has to do with the fact that I’m using a .NETCoreApp,Version=1.0 environment. Hopefully in the RTM version this option can be picked as well.
I would than implement it with someting like this:

var cert = new X509Certificate2(@"SomeAwesomeSharedLocation\startpage.pfx", "password");
options.ProtectKeysWithCertificate(cert);

Some issues I encountered

I got the following error “the payload was invallid”.
After some debugging I found that the purpose string difference between controllers that extend the base controller. This was due to the following line of code

var name = GetType().FullName;

On line 27:
2016-06-25 14_37_12
This resulted in different purpose strings under the DataProtector instance:

2016-06-25 14_31_08

Visualized by the following image (from the asp.net website)

purposes

Meaning that the AccountController could Unprotect the data, as it was the one Protecting it in the first place, but other controllers could not.
This was easily solved by using the following in the BaseController:

const string name = nameof (BaseController);

Things to consider

  1. While no certificate (pfx exported) is used, one is created at first use. If you lose the generated key you will not be able to restore your information. I.e. backup the keys;
  2. Changing the name of the BaseController, used as the purpose string, disables the IDataProtector from decrypting your data.

Comments are closed.