The problem:

Majority of ASP.Net applications (web-sites) store configuration settings in the .config file. The file is an XML file, and be can 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.

  1. Many web applications have database server 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.
  2. 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()
{
    // Get the current configuration file.
    System.Configuration.Configuration config =
    ConfigurationManager.OpenExeConfiguration(
    ConfigurationUserLevel.None);
    // Get the section.
    AppSettingsSection section = (AppSettingsSection)config.GetSection("appSettings");
    // Protect the section.
    section.SectionInformation.ProtectSection(
    "RsaProtectedConfigurationProvider");
    // Save the encrypted section.
    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:
http://www.4guysfromrolla.com/articles/021506-1.aspx

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.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"