Encrypting connection strings in Windows Azure web applications
You may not want your connection strings embedded in your web.config in plaintext â that will expose your database credentials to all the world. This blog post shows how to secure the connection strings. It is ugly, but quite doable.
Note: SQL Azure team has posted a four-part series on this in September 2010. Some details have changed since, and this post aims at being more practical, shorter and easier to follow. Iâll also discuss the common problems with the method. But feel free to read their version as well :-)
What do I need to do?
.NET Framework supports encryption of configuration elements per configuration section. Thus, youâll probably end up encrypting your whole connectionStrings section. This is likely to be just OK for you.
There are the following stages to the process:
Letâs go through these steps one by one.
Creating a certificate
You will need to create a certificate that is used as the encryption key. First you use this certificate to encrypt the configuration, then your site uses it to decrypt the configuration. To achieve your security gaols, you should make sure that the certificate and its password are safely stored. Ideally, your developers would never even see it.
Open up a Visual Studio Command Prompt (for example, a âVS2012 x64 Cross Tools Command Promptâ, âDeveloper Command Prompt for VS2012â or similar), and type the following. âMyApp Connection Stringsâ is just a name for the certificate, and the rest of MyApps are file names. There is no magic in those names.
makecert -r -pe -n "CN=MyApp Connection Strings" -sky exchange "MyApp.cer" -sv "MyApp.pvk"
You will need to type a password for the certificate (thrice). Keep it safe, because youâll need it. Next, youâll need to merge the .cer and .pvk files into another certificate file format .pfx. Do this with the following command line:
pvk2pfx -pvk "MyApp.pvk" -spc "MyApp.cer" -pfx "MyApp.pfx" -pi "password"
Put the password you set earlier into the argument value for the âpi switch.
Importing the certificate locally
To be able to encrypt the configuration, you must import the certificate on a workstation. Tap Windows-R and run mmc.exe to open up the Management console. Add the Certificates snapin by using File > Add/Remove Snapin and enabling Certificates snap-in. Select the âComputerâ account in the next dialog, and âLocal computerâ in the next one.
Once you have added the snapin, open the Personal store, right-click to open the All Tasks menu and choose Import.
Use the import wizard to import the certificate. By default, the file browser filters to *.cer files, which contain only public key information useful for encrypting the configuration information. The default options for the following dialogs are fine.
Under normal circumstances, you should only distribute the .cer file to people who need to encrypt the configuration. The .pfx (and .pvk) files contain the private key that can be used to decrypt the configuration â that should be kept behind locks and only installed into Azure. But for practical purposes, you may want to install the .pfx at this point so you can actually test the thing before pushing it out. If you do import the pfx, you also need to enter the password you chose earlier.
Adding the encryption provider
The pfx file represents the certificate format known as PKCS #12. To use the certificate in your application, you need to write a class that supports said encryption format and still works in Windows Azure. Microsoft actually provides one, but it comes in a Visual Studio 2008 solution. You can just get the relevant class file from here: Pkcs12ProtectedConfigurationProvider.cs.
Add the class into your solution. You can put it wherever you want, but the place must be precompiled into an assembly (i.e. an MVC project, class library, just not ASP.NET App_Code or other runtime compilation spots). You may need to add some assembly references (System.Configuration, System.Data, System.Security, System.Xml) to make it work. Change the namespace to something you want to see in your application: you will need the full type name later on.
Adding the certificate to your solution
In Solution Explorer, open the Roles node under your Cloud Service Project and open the Properties of your web role whose configuration you want encrypted. Choose Certificates and Add the Certificate. The default store location of âLocalMachineâ and store name of âMyâ are good. The Name field defaults to something like âCertificate1â, but that is not important.
Click the ââŚâ button in the Thumbprint field to select the certificate. Youâll get the dialog seen on the right.
Once you have picked the certificate, you will get a thumbprint value that is a hex string (â64DC1BB08D4E993D6D2A20BB543079F55968F4F4â). This value identifies your certificate, and in order for your next cloud publishing to succeed, a certificate with this thumbprint must already be uploaded to the Azure.
Encrypting your configuration
First, set up your configuration encryption provider by adding the following section to your web.config. Remember that if you have a configSections block, it must be the first block in the configuration file. In fact, encrypting your configuration deletes your configSections if it isnât the first section â thatâs probably a bug.
<configProtectedData> <providers> <add name="Pkcs12Provider" thumbprint="64DC1BB08D4E993D6D2A20BB543079F55968F4F4" type="MyApp.Utils.Pkcs12ProtectedConfigurationProvider, MyApp"/> </providers> </configProtectedData>
Remember to replace the exampleâs thumbprint with your own certificateâs thumbprint you got from the previous step. If you lost it, just open the Certificate properties page for your Web Role under the Cloud Service project. Also, make sure the type attribute contains the full namespaced name of your provider and the assembly filename after the comma (i.e. âMyApp.Securityâ for MyApp.Security.dll).
Now it is time to actually encrypt your connection strings, which is the ugly part. The encryption is done by issuing the following command in the Visual Studio command prompt, while in the same directory as your web.config. The parameters you pass in are the name of the configuration section to be encrypted, the path of the configuration file and the name of the provider.
aspnet_regiis -pef "connectionStrings" "." -prov "Pkcs12Provider"
But donât do it just yet. If you do, you will get an error on the lines of âCould not load file or assembly âMyAppââ, referring to the assembly that should contain the provider.
Note: If you run this in a directory that doesnât have a web.config to encrypt, youâll get an error message stating âThe protection provider âPkcs12Providerâ was not found. Strange error, but check your current working directory.
Aspnet_regiis will look for the specified assembly from a very limited set of lookup paths. First and foremost, it looks for the assembly in the GAC. Since it is typically cumbersome â and even bad practice â to register your application DLLs in the GAC, you shouldnât do that.
Microsoftâs canned solution (to which I linked above) works around this by wrapping the configuration provider in a separate DLL, which is then strongly named. The solution also contains an installer project that puts the provider into the GAC. But since the installer project doesnât build in new versions of Visual Studio, that is fairly clumsy. That said, if your organization already has a tool DLL that is typically deployed into GAC, you can include the provider in that assembly. If you do that, everything should just work (but of course, you need to refer to the assembly with its full name and all the attributes).
So, the next issue: How to make aspnet_regiis find your DLL without pushing it into GAC?
The easiest approach is to copy your DLL into the directory where aspnet_regiis.exe resides, typically C:\Windows\Microsoft.NET\Framework\v4.0.30319 or something similar. You end up putting your binaries behind .NETâs internal ones, which isnât very nice, but it works.
If you want avoid some of that mess, put the DLLs in a separate directory as outlined in a StackOverflow answer. Unfortunately, the directory must still reside under aspnet_regiis.exeâs location. And you can go further with features such as DEVPATH, but I wouldnât bother unless you really need to.
Once youâve resolved the DLL question and ran aspnet_regiis successfully, your configuration file should get properly encrypted. If you did install the .pfx into your certificate store, you can now also test this by running your application: if everything works, your application most obviously can access the encrypted configuration.
In case the Pkcs12ProtectedConfigurationProvider throws an ArgumentNullException with the parameter name of keyObject when youâre accessing the configuration, you probably have not imported the private part of the key (i.e. you chose the .cer file when importing).
Note that you donât have to modify your callsites for accessing the configuration at all: .NET Framework ConfigurationManager and other classes take care of invoking the decryptor when needed.
Uploading the certificate to Azure
The only thing left to do is to push the certificate to Windows Azure. Open the Azure Management Portal, choose your Cloud Services and get over to the Certificates tab.
Click the Upload button on the bottom of the screen to choose your certificate, choose your .pfx file and enter your password.
Note: If you upload the .cer file, Azure will accept it, but you will get the (already mentioned) ArgumentNullException for parameter keyObject when trying to access the configuration.
Once youâre done with this, publish your new package to the cloud. If everything went as planned, you should now have a working solution.
Practical tips and tricks
There are quite a few ways to manage the encryption process in real development scenarios. For most teams, it is worth encrypting only the production Database (and Azure Storage) access credentials.
The easiest way to this goal would probably be this:
- Designate an IT pro, lead developer or another liason who is allowed to know the production credentials.
- Have the trusted person install the public key (.cer file) on his computer, making him able to encrypt the credentials.
- Make your version-controlled web.config have the development configuration; typically plaintext is just fine here.
- Once the trusted person has created an encrypted connectionStrings section, put that in a config transformation file with the xdt:Transform=âReplaceâ approach. You will also need to use xdt:Transform=âInsertâ to add the configProtectedData section.
- You can now freely check in the config transformation as well.
- As is normal with encryption keys, store the private key (.pfx) and its password carefully, preferably in separate locations.
Finally, here is an example of a transformation file that handles the described scenario.
<?xml version="1.0"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <connectionStrings configProtectionProvider="Pkcs12Provider" xdt:Transform="Replace"> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <KeyName>rsaKey</KeyName> </KeyInfo> <CipherData> <CipherValue>A4K8W5Hr...</CipherValue> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue>G5cpXKvbY...</CipherValue> </CipherData> </EncryptedData> </connectionStrings> <configProtectedData xdt:Transform="Insert"> <providers> <add name="Pkcs12Provider" thumbprint="64DC1BB08D4E993D6D2A20BB543079F55968F4F4" type="MyApp.Utils.Pkcs12ProtectedConfigurationProvider, MyApp"/> </providers> </configProtectedData> </configuration>
Have a nice encryption!