Securing web.config passwords and secrets in .NET

Sometimes we need to access remote systems that cannot use single sign-on or Kerberos, and are forced to store sensitive information in the web.config file. This could potentially be an issue if the server has access by several admins. In this case, you could encrypt the passwords.

If you have the ability to add malicious code, reverse engineer the DLL etc, then sure you can get around it but it is an adequate solution for stopping people with read access to see your secrets.

I wanted a simple solution to read/encrypt the appSetting values only, so wrote a class that:

  • Checks for the unencrypted value
  • Encrypts the value using a salt and in the scope of the IIS user account (you should use an AD account for your app pool in IIS)
  • Saves the new encrypted value and deletes the unencrypted value

 

You could then use the class as:

var password = SecureConfig.AppSettings("MyPassword");
var credentials = new NetworkCredential { 
    Domain ="MYDOMAIN", 
    UserName = "MYUSERNAME", 
    SecurePassword = password 
};

The entire code is below. You need to add a reference to System.Security.

namespace MyApp.Models
{
    using System;
    using System.Security;
    using System.Security.Cryptography;
    using System.Web.Configuration;

    /// <summary>
    /// Handles reading and encrypting AppSetting values from the web.config.
    /// </summary>
    public class SecureConfig
    {
        /// <summary>
        /// The entropy used as salt for the encryption.
        /// </summary>
        private static readonly byte[] Entropy = System.Text.Encoding.Unicode.GetBytes("REPLACE WITH RANDOM STRING OF YOUR OWN");

        /// <summary>
        /// Gets the app setting as a <see cref="SecureString"/>. If the value exists, then it will be encrypted within the web.config file.
        /// </summary>
        /// <param name="key">The appSetting key.</param>
        /// <returns>A <see cref="SecureString"/>.</returns>
        internal static SecureString AppSetting(string key)
        {
            var value = WebConfigurationManager.AppSettings[key];
            if (!string.IsNullOrEmpty(value))
            {
                // The password is updated. Encrypt it then delete.
                var secure = EncryptString(ToSecureString(value));

                // Erase from web.config and store encrypted value
                var config = WebConfigurationManager.OpenWebConfiguration("~");
                config.AppSettings.Settings.Remove(key);
                config.AppSettings.Settings.Remove(key + "Encrypted");
                config.AppSettings.Settings.Add(key, string.Empty);
                config.AppSettings.Settings.Add(key + "Encrypted", secure);
                config.Save();
            }

            return DecryptString(WebConfigurationManager.AppSettings[key + "Encrypted"]);
        }

        /// <summary>
        /// Decrypts the string.
        /// </summary>
        /// <param name="encryptedData">The encrypted data.</param>
        /// <returns>The decrypted string as <see cref="SecureString"/>.</returns>
        private static SecureString DecryptString(string encryptedData)
        {
            try
            {
                byte[] decryptedData = ProtectedData.Unprotect(
                    Convert.FromBase64String(encryptedData),
                    Entropy,
                    DataProtectionScope.CurrentUser);
                return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData));
            }
            catch
            {
                return new SecureString();
            }
        }

        /// <summary>
        /// Encrypts the string.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>The encrypted string.</returns>
        private static string EncryptString(SecureString input)
        {
            byte[] encryptedData = ProtectedData.Protect(
                System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)),
                Entropy,
                DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }

        /// <summary>
        /// Converts a <see cref="SecureString"/> to plain text.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>The <see cref="SecureString"/> as a string.</returns>
        private static string ToInsecureString(SecureString input)
        {
            string returnValue;
            var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
            try
            {
                returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
            }

            return returnValue;
        }

        /// <summary>
        /// Converts regular text to <see cref="SecureString"/>.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>A <see cref="SecureString"/>.</returns>
        private static SecureString ToSecureString(string input)
        {
            var secure = new SecureString();
            foreach (char c in input)
            {
                secure.AppendChar(c);
            }

            secure.MakeReadOnly();
            return secure;
        }
    }
}