Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault

The Data Protection API (DPAPI) is an essential service in ASP.NET Core that is often overlooked. This post will give an overview of what it does and how we can store its encryption keys in Azure Key Vault.

The API’s main purpose is to encrypt and decrypt data. For example, it is used to:

  • Protect the various cookies issued by ASP.NET Core.
    I blogged about how the cookies are protected in this blog post.
  • Protecting the OpenID Connect state and nonce parameters.
    You can read about these two parameters in my blog post here.

The API is mainly used to encrypt and secure short-lived data (from minutes to months), but nothing stops you from encrypting data in the long-term.

How can you use the Data Protection API to protect your own data?

The API is typically used in ASP.NET Core applications, but nothing prevents you from using it in, for example, a console application. Just add the Microsoft.AspNetCore.DataProtection.Extensions  NuGet package, and then you can start using it right away.

Here is a sample console application using the Data Protection API to protect and unprotect a string:

				
					// Initialize the Data Protection system and store the keys c:\temp\keys
var provider = DataProtectionProvider.Create(new DirectoryInfo(@"c:\temp\keys"));

// Create a data protector
var protector = provider.CreateProtector("MyAppName");

string encryptedData = protector.Protect("Hello DPAPI");

string decryptedData = protector.Unprotect(encryptedData);

Console.WriteLine(encryptedData);
//CfDJ8PEJDb99-ddAhkM_GeKgmn1fcrWF9LGi10rTkU1f7IbW2y282ISIXZTgBBy_cL9iQ2RzxCyKbLlku7PlWVcTJw11NylWVCDqkdpjmsbm9U1chfg-rYUeBNFGwB-Q5q9b1w

Console.WriteLine(decryptedData); //Hello DPAPI

				
			

Interestingly, the API only works with strings, making it very easy to use. If you need to protect binary data, you can base64 encode it before you protect it.

How to use the Data Protection API in ASP.NET Core?

Using it in ASP.NET Core is also straightforward, as the Data Protection API is typically included by default in most ASP.NET Core projects. To use it in a controller, you can ask the Dependency Injection container for an instance of IDataProtectionProvider.  Like this:

				
					public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger, 
                          IDataProtectionProvider protectionProvider)
    {
        _logger = logger;

        // Create a data protector
        var protector = protectionProvider.CreateProtector("MyAppName");

        var encryptedData = protector.Protect("Hello DPAPI");

        var decryptedData = protector.Unprotect(encryptedData);

        Console.WriteLine(encryptedData);
        //CfDJ8Dsx1cA547pJnoAz-iXx9XYVi8GMYBHcEU9ceuB4Ep75Yr1I4pX4Rw3DWIE4H4cigg_-NvixoAoR5vl641cT-FEagETLBRoecfX011zsX7aPqf4OjxGNpCh-Bk6qfxgphw

        Console.WriteLine(decryptedData); //Hello DPAPI
    }
    ...
}

				
			

The key ring

To protect the data, DPAPI uses an encryption key; if no key is found, it will automatically create one for us. DPAPI will introduce new keys regularly (default once every 90 days), meaning many keys will be involved over time. All these keys are stored in a key ring, and this key ring contains both the active key and the past keys.

You need to keep the old keys around if you want to be able to decrypt already encrypted data.

Persisting the key ring

The key ring should be persisted somewhere outside of your application, and there are many ready-made options to choose from, many are provided by Microsoft and the community.

However, one missing option is Azure Key Vault. In this post, we’ll write a sample provider for Azure Key Vault to showcase how simple it is to implement.

Why store the key ring in Azure Key Vault?

Most services will typically need all or some of these items to operate:
Using Azure Key Vault to provide all of the above could simplify your infrastructure architecture, and AKZ can securely store all of them.

It can even create some items for you; for example, if you use services like IdentityServer, it can create the token signing keys for you. This post will focus on persisting the key ring in Azure Key Vault.

Encrypting the keys in the key ring

Optionally, the keys inside the key ring can be encrypted at rest (when stored), and there is already support for that provided by using the Azure.Extensions.AspNetCore.DataProtection.Keys  Nuget package from Microsoft. However, storing the key ring and the encryption key in the same location might be overkill.

The Data Protection API and security

Don’t forget to always ensure that you use separate key rings for your different environments so that if one of the key rings is compromised, it can’t be used against the other environments.

What does a key in the key ring look like?

The key ring is by default represented as an XML document, and here’s a sample unencrypted key when it is stored in Azure Key Vault:

				
					<root>
  <key id="66533236-1e0f-40cb-b6ca-3cdb3f942331" version="1">
    <creationDate>2024-02-12T15:47:21.2522781Z</creationDate>
    <activationDate>2024-02-12T15:47:19.8787424Z</activationDate>
    <expirationDate>2024-05-12T15:47:19.8787424Z</expirationDate>
    <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
      <descriptor>
        <encryption algorithm="AES_256_CBC" />
        <validation algorithm="HMACSHA256" />
        <masterKey p5:requiresEncryption="true" xmlns:p5="http://schemas.asp.net/2015/03/dataProtection">
          <!-- Warning: the key below is in an unencrypted form. -->
          <value>xtlj8GlTE2t4OeoabEBG7Ry8XVXYzaTvxsM4UPs8kQYlepNM2/6FsmxRVE+BavFKj3ULwAiuqKmyC47QmCPLYw==</value>
        </masterKey>
      </descriptor>
    </descriptor>
  </key>
</root>

				
			

You can see above that the key is not encrypted at rest.

An example of a key ring where the keys are encrypted using a separate key in Azure Key Vault can look like this:

				
					<root>
  <key id="77f04bd2-3394-4e2c-a101-d47a0cb9f04c" version="1">
    <creationDate>2024-02-13T08:30:51.9976822Z</creationDate>
    <activationDate>2024-02-13T08:30:50.8851358Z</activationDate>
    <expirationDate>2024-05-13T08:30:50.8851358Z</expirationDate>
    <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
      <descriptor>
        <encryption algorithm="AES_256_CBC" />
        <validation algorithm="HMACSHA256" />
        <encryptedSecret decryptorType="Azure.Extensions.AspNetCore.DataProtection.Keys.AzureKeyVaultXmlDecryptor, Azure.Extensions.AspNetCore.DataProtection.Keys, Version=1.2.2.0, Culture=neutral, PublicKeyToken=92742159e12e44c8" xmlns="http://schemas.asp.net/2015/03/dataProtection">
          <encryptedKey xmlns="">
            <!-- This key is encrypted with Azure Key Vault. -->
            <kid>https://dpapikeyvault.vault.azure.net/keys/MyDPAPIKey/c07cda675a3446f6a752a1e50c17c0c7</kid>
            <key>ZSWcsd302ouGxLFtx26MeuioxCRFhcwKxNTK7qL6TrG0d7GoiHV2B/Xtjr9CKNPgwkIUXv/mhTb/+lvwL15i/hh4kyYAPD/OftQjaHy3RRvHSTtkWZiCivyQr6PD8LbbZ4Wkznayzla60Ulwi+1TGhmUaGMTvpSo1mERVCK8iUEU7l8k1BCvJbEGn721nYvdBZQlBraPYNLaRDxQ3JCi6Brl4UImQwetByip2YPDWcKOMVMTH+JliVGrdZclLHk97E5MCIq1G4l+C5urQ6c8C1h+vJYQJ+5581eubGLXbC5pATa5bY8xCGObVj/6mZ5k9poOo+iKOU2OzL0N1h/leA==</key>
            <iv>M/Jh3zr94JYs2BYikdmJiA==</iv>
            <value>3lR+foXTx4awY5KbSNzZLGXfxtu0M9vaN0gc7XBDUwwbtcDloQyuHLI9T7l/FDmtdi/KO9pas1V5VBq/yVSdufmu6JnQmVIA8oHDodBD+T7+h5pM9zfP6Z6n7LcMuAxFv4T9PUEZIpThVB2dlC3pW5RNEWuFYP6oMPbk5RZw8V6dSydpBr6GWZXLUuFB3oAJB7+R/VCPtZypdYElZPVywdoEnLYarsoMuloqYAFz+3oP/y1XmHOZx5z1uYBw5B/3r851mYm9r7B5qU5LEV2Jq5CDMUbHgZAmca1cxuM5LKrBim74hYY03vtYulfxvae76aI2qNs91LmUfQTZklMskY+WA3AbEt0aUCo49n5mA+Ywffh1vpukoYRiC+7F1uC82Fea1GO0mHeGYzhHC517xbgQSON0PS3QxpOqkr8b7f8=</value>
          </encryptedKey>
        </encryptedSecret>
      </descriptor>
    </descriptor>
  </key>
</root>

				
			

Does Azure Key Vault Have Limitations?

Yes, Azure Key Vault has some limitations around storing the key ring as a secret. The two main things to keep in mind are:

  • We should store the key ring as one big secret in AKV, not the keys individually. This is to avoid making too many requests to AKV.
  • There is an upper size limitation of around 25KB for a secret, meaning that there is an upper limit on the number of keys to keep in the key ring. My calculation estimates that this gives us enough space to hold about 12-13 keys, which should be sufficient for the most common use cases. This might, however, be a problem if you want to persist all the keys long-term and rotate them or revoke them regularly. Applying compression could increase this number.

Implementing the Azure Key Vault store

Implementing a key-ring store is very simple; all you need to do is implement the following interface:
				
					/// <summary>
/// The basic interface for storing and retrieving XML elements.
/// </summary>
public interface IXmlRepository
{
    /// <summary>
    /// Gets all top-level XML elements in the repository.
    /// </summary>
    IReadOnlyCollection<XElement> GetAllElements();

    /// <summary>
    /// Adds a top-level XML element to the repository.
    /// </summary>
    /// <param name="element">The element to add.</param>
    void StoreElement(XElement element, string friendlyName);
}

				
			
A sample implementation and example project can be found in my GitHub repository here . Feel free to submit a pull request if you have ideas for improving the code.

Conclusions

Using Azure Key Vault to store the key ring is possible and implementing the persitence layer to support this is straight foward.

Feedback, comments, found any bugs?

Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details here.

About the author

Hi, I’m Tore! I have been fascinated by computers since I unpacked my first Commodore VIC-20. Today, I enjoy providing freelance development and developer training services, focusing on ASP.NET Core, IdentityServer, OpenID Connect, Architecture, and Web Security. You can connect with me on LinkedIn and Twitter, and you can find out more about me and my services here, as well as my courses for developers, including my course, Introduction to IdentityServer and OpenID-Connect.

Related posts by me:

Share This Story
Share on linkedin
Share on twitter
Share on facebook
Share on pinterest
Share on email

About me

My name is Tore Nestenius and I’m a trainer and senior software developer focusing on Architecture, Security and Identity, .NET, C#, Backend, and Cloud, among other things.

Do You Want Tore To Be Your Mentor?

Categories