Friday, July 11, 2014

Encrypting Configuration Sections In .NET

Please note before reading - while you might of found this solution from a search result and the post looks long and possibly intimidating - it is not! Once you familiarize yourself with the steps, the process becomes quite easy to duplicate per machine where required. Most of the post here is providing explanation of what's happening as opposed to the raw steps needed to preform the encryption/decryption.

I have about a half of dozen posts in 'draft' form partially done that I want to get off my plate, so in no particular order here is one I've had on the back burner for a while. I'll guide you through encrypting configuration sections in application .config files. Nothing cutting edge her, but still an important topic to cover.


This is applicable to any type of .config such as a web.config or an app.config file so that means it spreads the technology spectrum of ASP.NET, WinForms, WPF, Windows Services, etc.. I have not yet looked into the equivalent for Win8 Store Apps using LocalSettings or RoamingSettings, so for now this is applicable to the aforementioned that use .config files. Since the LocalSettings are buried in .dat files in the user's profile the need may not be as pressing as the .config files that reside in the virtual directory of a web site directly in the well known inetpub\wwwroot.


So often I'm reading through a book, magazine, or online article and I see the following:

<connectionStrings>... with no encryption

or:

<appsettings>... with no encryption

OK I get it, the authors typically do not have the time or want to go into a major tangent to talk about securing these elements with encryption. However today that's exactly what I'm going to show you how to do. If for some reason you have not caught on yet, you do not want sensitive information that is displayed above in plain text. If the file is compromised (internally or externally in the wrong hands for 1 of 100 reasons) it's a good layer of protection against being able to read it directly without decryption. In any regard, you as a developer should ALWAYS be thinking about security and protecting any type of sensitive data as if it's your own.

The steps are really straight forward and the process is quite simple and repetitive once you get used to it. I recommend you make a cheat sheet of notes for encrypting and decrypting the configuration sections to aid you on an ongoing basis. Steps 1-2 below only have to be done 1 time. Steps 3-4 are only done 1 time per machine where decryption will take place. Steps 5-6 are the steps ongoing to encrypt and decrypt configuration sections when needed.

NOTE: All steps below must be done running the command line tool as an Administrator. If you do not do this you will get various errors when trying to create/export keys as well as with manipulating permissions.

1. Create a machine-level RSA Key Container (1 time step)

Let's being by talking about the default provider and why it will not suffice for encryption / decryption needs outside of a single machine. 


If you use the default RSAProtectedConfigurationProvider without specifying a a Custom RSA Key Container, the encryption/decryption will only work on the machine where the data is 1st encrypted. Obviously this solution is no good as you will more than likely develop a solution locally and publish/deploy to 1...n servers. In this scenario, you need to create a Custom RSA machine-level container key and export it to a file which can then be imported on the servers where the application will run. 

If you try and decrypt the .config file manually using the command line with the default provider on a secondary machine where the encryption was not done, you will receive the following error below. Also in your .NET application at runtime, you would get an error upon trying to access any of the settings that have been encrypted.


Also before we get started we need to be aware of some permission issues. To prevent the following error: "Creating RSA Key Container... The RSA key container could not be opened. Failed!" message upon creating a new key, you will 1st want to set up permissions on the following directory where the machine keys reside after being creating: 

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys


This is the directory where the machine keys from the command line below get created and stored. The issue is, even as an administrator you may not have access to create and manipulate the keys by default. The easiest thing to do is allow the 'Administrators' group of the machine have 'Modify' permissions to this directory.


Right-click on the 'MachineKeys' directory and ensure the Administrators group has the proper access:



Now that the permissions are set, we can being the process of creating our container. To create a Custom RSA Container run the following command:

aspnet_regiis -pc "SecurityKeys" -exp 

Note: the -exp switch allows the keys to be exportable (next step)


You should see the following success message:


2. Exporting the Custom RSA Encryption Key (1 time step)

Note: If just running through these steps and you want to see how it all works on a single machine locally, you can skip Step #2 and #3 and come back later to export and import to additional machines.

We must export the newly created encryption key so it can then be imported on 1...n machines where our app will run. This way we know decryption will occur seamlessly once we deploy.

To export the custom RSA key container to a file run the following command:

aspnet_regiis -px "SecurityKeys" "C:\SecurityKeys.xml" -pri

Note: the -pri switch makes sure both the private and public keys are exported. This enables both encryption and decryption. Without the– switch, you would only be able to encrypt data with the exported key.


You should see the following success message below. Also note the .xml file created in the location you specified in the command.


3. Importing the Certificate (1 time step - per machine)

Next we must import the .xml file containing the RSA encryption key on the machine(s) where our app will be running. Obviously we do not need to import it on the current machine because we have already created it in the machine keys. However, you must copy that file to the servers/machines where the app will run and import it.

I would assume the PowerShell gurus could script or automate this process rather quickly across machines. I'll just show the command required.

To import the RSA Encryption Key, run the following command:

aspnet_regiis -pi "SecurityKeys" "C:\EncryptionTest\SecurityKeys.xml"

You should see the following success message:



NOTE: It is important to DELETE the .xml file containing the keys once they have been successfully imported. This way the keys don't fall into the wrong hands and get imported on a machine where not desired. You can always go back to the main machine and export again as needed.

4. Adding Permissions to the Certificate (1 time step - per machine)

The certificate we just created now needs to have the proper permissions added to it so decryption can happen at runtime automatically under the context the app is running. If you are running an ASP.NET app in IIS that will be NT Authority\NETWORK SERVICE. The list of users or groups that need to have permission depends on they type of app you are running. If it's a WinForms or WPF app, you might add a group like CompanyXYZ\MyAppUsers. If you get any type of runtime errors along the lines of:

Failed to decrypt using provider 'AppEncryptionProvider'. Error message from the provider: The RSA key container could not be opened.

...then come back to this section and make sure to grant access both to the key and at the MachineKey folder to the user context for which your app/service/etc. is running under.

First we need to add access to the key container itself. For this example we'll assume we are running a web application using ASP.NET. To add access to the container run the following command:

aspnet_regiis -pa "SecurityKeys" "NT Authority\NETWORK SERVICE"

You should see the following success massage:


Second, we need to go back to the directory from step #1 and add 'Read' permissions only to the same user. To recall that directory is as follows:

C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

Right-click the 'MachineKeys' folder and grant just 'Read' access to 'NETWORK SERVICE' as follows:



5. Encrypting the Configuration Section

Now it's finally time to encrypt!! Copy the full path to the directory containing your web.config or app.config file. Notice this super secret I have in plain text that we want to encrypt:


<appSettings>
    <add key="SuperSecretPassword" value="abc123" />
</appSettings>

Note: This process uses the aspnet_regiis.exe tool and targets web.config files by default. However, this will still work for any type of app.config file as well. Just close any open instances of your app.config file in VS.NET and rename it temporaily to web.config for the encrypting process. Once complete, rename back to app.config and open back up in VS.NET. You will see the encryption still works perfectly.

Add the following configuration section above the and section in your web.config or app.config file. The name property will be the handle we will use for encrypting from the command line and not the key container name, so just remember this so as not to get confused:


<configProtectedData>
  <providers>
  <add keyContainerName="SecurityKeys"
    useMachineContainer="false"
    description="Uses RsaCryptoServiceProvider to encrypt and decrypt"
    name="AppEncryptionProvider"
    type="System.Configuration.RsaProtectedConfigurationProvider,
    System.Configuration, 
    Version=2.0.0.0, 
    Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a" />
  </providers>
</configProtectedData>

Close any open .config file being targeted, and run the following command to encrypt the section sepecified. You can change the section to be encrypted as needed:

aspnet_regiis -pef "appSettings" "C:\EncryptionTest" -prov "AppEncryptionProvider"

The values after the -pef switch indicates the section to encrypt. The value after the -prov (provider) switch should be the value from the 'provider name' property we set in the config file above. You should see success messages like the ones below. I encrypted both the <appsettings> and <connectionstrings> sections:



Now open back up the web.config or app.config file and the sections are encrypted!



6. Decrypting the Configuration Section

Decrypting the file as we will see in step #7 happens automatically upon calling any settings in code, but obviously the resulting encrypted sections do not allow you to make changes. You may need to decrypt the .config file to get the sections in a state where changes can be made.

To decrypt the .config file, run the following command (note the provider switch is not needed):

aspnet_regiis -pdf "appSettings" "C:\EncryptionTest"

You should see the following success message:


Open the file back up and it should be decrypted so changes can be made.

7. Seeing it in action within the application

Guess what code you need to access the <appsettings> or <connectionstrings> values in code to ensure it gets decrypted properly? Nothing!! That's why this is so great, all of it was handled in the configuration by the configured provider and Key Container.


Look at the following line of code in action! It was decrypted on the fly seamlessly and no special coding was needed. That's nice!


This is a really simple way to add security to those elements that are sensitive in your configuration files. So instead of having your entire database connection string in plain text within your .config file, consider taking 2 minutes and encrypting it!

5 comments:

  1. Should the config file really contain useMachineContainer="false" given that this is a machine-level key?

    ReplyDelete
  2. config file should contain useMachineContainer="true", not "false"

    ReplyDelete
  3. A little late to the party but I couldn't get it to work with useMachineContainer="false" like others said. (at least when exporting to other devices in a windows application)

    ReplyDelete
  4. Can you encrypt the identity section in the web.config file? I am getting error 500.19

    ReplyDelete
  5. This is really the best source I have found for "Machine-level" encryption.

    You might want to update your article to include Windows 10 directory set up.

    Instead of "C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys", Windows 10 uses "C:\Users\All Users\Microsoft\Crypto\RSA"

    ReplyDelete