I recently had a requirement to encrypt some data stored in a Web.config file for an Azure hosted service that’s accessed over HTTPS.
To help secure information in configuration files, ASP.NET provides a feature called protected configuration, which enables the encryption of sensitive data in a configuration file. The recommended approach is to protect configuration using either the DpapiProtectedConfigurationProvider class or the RsaProtectedConfigurationProvider class that are both included in the .NET framework.
Unfortunately these protected configuration providers do not work with Azure. The DpapiProtectedConfigurationProvider class uses a machine-specific key that cannot be transferred to Azure. While the RsaProtectedConfigurationProvider enables transferring an RSA key pair in an XML file to different machines, and then importing the key to a key container, the XML file is meant to be removed from the machine after the key has been imported. On Azure, since the account running the Web role doesn’t have permissions to delete files in the web root, it is not possible to remove the XML file.
I needed a solution that would:
- Allow data to be decrypted when running the service locally, and when running the service in Azure.
- Allow data to be encrypted and decrypted on any machine in our organization.
- Not be so onerous to prevent the periodic changing of the data to be encrypted/decrypted.
The recommended approach for Azure is to use the Pkcs12 custom protected configuration provider and the Aspnet_regiss.exe tool to encrypt sections of the configuration file. The Pkcs12 format enables transfer of certificates and their corresponding private keys from one machine to another. This provider is similar to the RsaProtectedConfigurationProvider, with the difference being that instead of transferring the RSA key pair in an XML file, it relies on the transfer to occur using a certificate in .PFX format. This approach has been used by P&P in the Autoscaling Application Block. While this provider works with the built-in tooling in ASP.NET that can read configuration automatically, it is an onerous solution for configuration that may change periodically.
This blog post details my solution, which was to use the SSL cert for the service to encrypt data once on a local machine, and then use the SSL certificate to decrypt data both locally and in Azure. The advantage of this approach is that for a service delivered over HTTPS, Azure will already have the SSL certificate stored in its certificate store, and so no additional key transfer is required.
Implementation
To encrypt a piece of data you must first retrieve the SSL certificate from its location in the certificate store, and then encrypt the data using the certificate. The following code example shows this process.
1 private static string Encrypt(string plainText)
2 {
3 // Thumb value for SSL cert
4 var thumb = "<thumbprint goes here>";
5
6 var passwordBytes = UTF8Encoding.UTF8.GetBytes(plainText);
7 var contentInfo = new ContentInfo(passwordBytes);
8 var env = new EnvelopedCms(contentInfo);
9 X509Store store = null;
10 string cipherText = null;
11
12 try
13 {
14 store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
15 store.Open(OpenFlags.ReadOnly);
16 var cert = store.Certificates.Cast<X509Certificate2>().Where(xc => xc.Thumbprint == thumb).Single();
17 env.Encrypt(new CmsRecipient(cert));
18
19 cipherText = Convert.ToBase64String(env.Encode());
20 }
21 finally
22 {
23 if (store != null)
24 store.Close();
25 }
26 return cipherText;
27 }
The X509Store class is used to provide access to the X.509 store, which is the physical store where certificates are persisted and managed. Once the store has been opened in read only mode, the SSL certificate is retrieved by searching for its thumbprint value. The data to be encrypted is stored in a CMS/PKCS #7 enveloped data structure, and is encrypted using the EnvelopedCms.Encrypt method. It’s then base64 encoded before being returned from the method.
Decrypting the data simply reverses the process. The same SSL certificate is retrieved from the certificate store, and then is used to decrypt the data. The following code example shows this process.
1 private static string Decrypt(string cipherText)
2 {
3 // Thumb value for SSL cert
4 var thumb = "<thumbprint goes here>";
5 X509Store store = null;
6 string plainText = null;
7
8 try
9 {
10 store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
11 store.Open(OpenFlags.ReadOnly);
12 var cert = store.Certificates.Cast<X509Certificate2>().Where(xc => xc.Thumbprint == thumb).Single();
13 var bytes = Convert.FromBase64String(cipherText);
14 var env = new EnvelopedCms();
15 env.Decode(bytes);
16 env.Decrypt();
17 plainText = Encoding.UTF8.GetString(env.ContentInfo.Content);
18 }
19 finally
20 {
21 if (store != null)
22 store.Close();
23 }
24 return plainText;
25 }
The X509Store class is used to provide access to the X.509 store, with the constructor taking arguments that indicate which part of the certificate store should be opened. Once the store has been opened in read only mode, the SSL certificate is retrieved by searching for its thumbprint value. The data to be decrypted is converted to a byte representation, from its base64 representation, before being decoded and decrypted by the EnvelopedCMS class. The plain text is then returned from the method.
This approach to encryption and decryption enables you to store sensitive information in your configuration file in an encrypted form, which can then be decrypted both when running the service locally, and when running it in Azure. It offers the advantage that it’s not necessary to transfer additional keys to Azure in order to perform decryption, and it’s not too onerous a task to change encrypted data periodically. It can be further strengthened through the use of additional security techniques, including using the SecureString class at appropriate places in the code.
Summary
This blog post has demonstrated how to encrypt and decrypt data using an SSL cert. My requirement was to encrypt/decrypt data stored in a configuration file, but it may equally be used with other data. The advantage of this approach is that it enables you to decrypt data both when running a service locally, and when running it in Azure, without the onerous task of copying additional encryption key data to Azure.