The Problem
Majority of .NET applications and web-sites store configuration settings in the .config file. The file is an XML file, and can be easily consumed from any .NET application using System.Configuration
library. The file frequently contains sensitive configuration settings such as authentication and authorization.
For instance, database connection string is frequently stored in application settings section as key/value pair:
<add key="MyConnectionString1" value="server=db1;
uid=readonlyuser;pwd=secret;database=topsecret;/>
By default, .config files are stored in plain text and can easily be read by anyone who has access to the server/application.
“So what!”, “Why is that a problem at all?”
Here are a few examples why that can actually be a huge problem.
- Many web applications have database servers that reside on a different machine from the web-server. It is done for security reasons and to improve performance. Since anyone who gets access to your web-server will easily read your configuration file, he will get access to your database server. And that might be highly undesirable because the database might contain usernames, e-mails, and other sensitive information.
- An application that needs database access is installed on a computer that many people have access to.
Of course, one can hard-code connection strings into the source-code but compiled code, even obfuscated, can easily be decompiled. In addition, changing the connection string can be a hell because application must be recompiled and reinstalled.
The Solution
In this article, I will present three solutions to the problem stated above. I will present a short version of the first two solutions and will present a detailed version of the third one. I will also attach the source code for the third solution.
The First Solution
Encrypt the password before the application is shipped and create a service in your database server that will intercept any login attempt and will decrypt the password. That is probably the most secure solution, but the problem is that this solution is not easy to implement.
The Second Solution
Use protected section mechanism (courtesy of .NET Framework).
static public void ProtectSection()
{
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
AppSettingsSection section = (AppSettingsSection)config.GetSection("appSettings");
section.SectionInformation.ProtectSection(
"RsaProtectedConfigurationProvider");
section.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
}
This approach can be compared to bringing home the entire orange tree, when you only need a bag of oranges because the entire section will be encrypted.
If that is what you want, here is a good article describing that approach.
The Third Solution (Preferred, By Me)
In this solution, only certain settings of a section are encrypted using DPAPI.
Unfortunately DPAPI is not a flawless solution:
DPAPI is a password-based data protection service. It requires a password to provide protection. The drawback, of course, is that all protection provided by DPAPI rests on the password provided. This is offset by DPAPI using proven cryptographic routines, specifically the strong Triple-DES algorithm, and strong keys, which we'll cover in more detail later. Because DPAPI is focused on providing protection for users and requires a password to provide this protection, it logically uses the user's logon password for protection.
Nevertheless, DPAPI offers a reasonably good protection for your sensitive setting.
It is worth noting that DPAPI security is machine specific, meaning that data encrypted on one machine cannot be decrypted on another machine.
Code Walkthrough
Settings to encrypt are specified in the <appSettings>
section of the configuration file.
<add key="KeysToEncrypt" value="ValuableKey1,ValuableKey2" />
Where, value
is a set of keys to encrypt.
Here are the settings we want to encrypt in this example:
<add key="ValuableKey1" value="Top Secret 1" />
<add key="ValuableKey2" value="Top Secret 2" />
Since DPAPI security is machine specific, we will need to encrypt the configuration file on the machine that it will run from (web-server, for web applications).
ConfigUtility.ConfigEncryptToFile();
ConfigUtility.ConfigDecryptToMemory();
ConfigEncryptToFile
method encrypts keys specified in “KeysToEncrypt
” and updates the configuration file:
public static void ConfigEncryptToFile()
{
string encrypted = ConfigurationManager.AppSettings.Get("Encrypted");
if (encrypted == null || !encrypted.Equals("True"))
{
string keysToEncrypt = ConfigurationManager.AppSettings.Get("KeysToEncrypt");
if (keysToEncrypt != null && keysToEncrypt.Length > 0)
{
Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
AppSettingsSection appSettings = config.AppSettings;
string[] keys = keysToEncrypt.Split(',');
foreach (string key in keys)
{
KeyValueConfigurationElement kv = appSettings.Settings[key];
if (kv != null)
{
kv.Value = EncryptUtility.EncryptString
(EncryptUtility.ToSecureString(kv.Value));
}
}
appSettings.Settings.Add("Encrypted", "True");
config.Save(ConfigurationSaveMode.Modified);
}
}
}
ConfigDecryptToMemory
reloads the configuration file and decrypts the keys:
public static void ConfigDecryptToMemory()
{
ConfigurationManager.RefreshSection("appSettings");
string encrypted = ConfigurationManager.AppSettings.Get("Encrypted");
if (encrypted != null && encrypted.Equals("True"))
{
string keysToDecrypt = ConfigurationManager.AppSettings.Get("KeysToEncrypt");
string[] keys = keysToDecrypt.Split(',');
foreach (string key in keys)
{
string value = ConfigurationManager.AppSettings.Get(key);
value = EncryptUtility.ToInsecureString
(EncryptUtility.DecryptString(value));
ConfigurationManager.AppSettings.Set(key, value);
}
}
}
It is important to note that if we are really paranoiac about security, it is better to decrypt encrypted keys only when we actually use them, and keep them as SecureStrings
in memory.
It is also worth noting that EncryptUtility
class contains entropy and security can further be improved by generating a pseudo-random “entropy”.
EncryptUtility
methods were written by Jon Galloway.