Since this ties to the connection strings, I decided a little refactoring was in order. I created a base class to contain all the shared functionality:
using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
namespace ProtectedConfiguration
{
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]}
public abstract class ProtectedConfigurationProviderBase : ProtectedConfigurationProvider
{
#region members}
// Provider name
private string pName;
// Create Entropy To salt the process aka 'Magic Salt'
private readonly byte[] entropy = Encoding.Unicode.GetBytes("magicsalt");
#endregion
#region Overrides of ProviderBase
//
// ProviderBase.Name
//
public override string Name
{
get { return pName; }}
#endregion
#region Overrides of ProtectedConfigurationProvider
//
// ProviderBase.Initialize
//
public override void Initialize(string name, NameValueCollection config)
{
pName = name;
base.Initialize(name, config);
}
#endregion
#region Methods
/// <summary>
/// Encrypt the passed string
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected string EncryptData(string data)
{
//convert to byte array}
byte[] valBytes = Encoding.ASCII.GetBytes(data);
// Use DPAPI to Encrypt
byte[] encryptedData = ProtectedData.Protect
(valBytes,
entropy, DataProtectionScope.LocalMachine);
//convert to base64 and wrap with xml tags
return Convert.ToBase64String(encryptedData);
/// <summary>
/// Decrypt the passed string
/// </summary>
/// <param name="encryptedData"></param>
/// <returns></returns>
protected string DecryptData(string encryptedData)
{
string password = encryptedData;}
//convert to encrypted byte array
byte[] valBytes = Convert.FromBase64String(password);
// Decrypt using DPAPI
byte[] decryptedData = ProtectedData.Unprotect
(valBytes, entropy, DataProtectionScope.LocalMachine);
var encoding = new ASCIIEncoding();
//convert to ascii
return encoding.GetString(decryptedData);
#endregion
I also made some changes to my ConnectionStringProtectedConfigurationProvider. I made it db agnostic, so any connection string, SqlServer, Oracle, DB2, etc. will work as long as it has the word "Password" in it. Of course I also changed it to inherit my base class above:
using System;
using System.Data.Common;
using System.Security.Permissions;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Specialized;
using System.Configuration;
namespace ProtectedConfiguration
{
public sealed class ConnectionStringProtectedConfigurationProvider : ProtectedConfigurationProviderBase}
{
#region Overrides of ProtectedConfigurationProvider}
/// <summary>
/// Encrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing encrypted data.
/// </returns>
/// <param name="node">The <see cref="T:System.Xml.XmlNode" /> object to encrypt.</param>
public override XmlNode Encrypt(XmlNode node)
{
XmlDocument doc = new XmlDocument();}
doc.PreserveWhitespace = true;
XmlNodeList nodeList = node.SelectNodes("/connectionStrings/add");
if (nodeList != null)
{
for (int i = 0; i < nodeList.Count; i++)}
{
XmlAttribute attribute = nodeList[i].Attributes["connectionString"];}
DbConnectionStringBuilder builder = new DbConnectionStringBuilder
{ConnectionString = attribute.Value};
if (builder.ContainsKey("Password") && !string.IsNullOrEmpty(builder["Password"].ToString()))
{
builder["Password"] = EncryptData(builder["Password"].ToString());
attribute.Value = builder.ToString();
}
doc.LoadXml("<EncryptedData>" + node.InnerXml +"</EncryptedData>");
return doc.DocumentElement;
/// <summary>
/// Decrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing decrypted data.
/// </returns>
/// <param name="encryptedNode">The <see cref="T:System.Xml.XmlNode" /> object to decrypt.</param>
public override XmlNode Decrypt(XmlNode encryptedNode)
{
//get each <add> in the connection string block}
XmlNodeList nodeList = encryptedNode.SelectNodes("/EncryptedData/add");
//start building the output xml string
StringBuilder output = new StringBuilder();
output.Append("<connectionStrings>");
if (nodeList != null)
{
for (int i = 0; i < nodeList.Count; i++)}
{
//get the text for the connection string}
XmlAttribute attribute = nodeList[i].Attributes["connectionString"];
//use DbConnectionStringBuilder to parse
DbConnectionStringBuilder builder = new DbConnectionStringBuilder
{ConnectionString = attribute.Value};
if (builder.ContainsKey("Password") && !string.IsNullOrEmpty(builder["Password"].ToString()))
{
//decrypt it}
builder["Password"] = DecryptData(builder["Password"].ToString());
attribute.Value = builder.ToString();
//add the connection string with decrypted password to output
output.Append(nodeList[i].OuterXml);
//add closing tag
output.Append("</connectionStrings>");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.LoadXml(output.ToString());
return xmlDoc.DocumentElement;
#endregion
And on to the AppSettings, nothing too fancy, at least after you've seen the above it's really just more of the same:
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Security.Permissions;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Specialized;
using System.Configuration;
namespace ProtectedConfiguration
{
class AppSettingsProtectedConfigurationProvider : ProtectedConfigurationProviderBase}
{
#region members}
private List<string> keys = new List<string>();
#endregion
#region Overrides of ProtectedConfigurationProvider
/// <summary>
/// Stores the list of keys that need encrypted
/// </summary>
/// <param name="name"></param>
/// <param name="config"></param>
public override void Initialize(string name, NameValueCollection config)
{
keys = new List<string>(config["keys"].Split(','));}
base.Initialize(name, config);
/// <summary>
/// Encrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing encrypted data.
/// </returns>
/// <param name="node">The <see cref="T:System.Xml.XmlNode" /> object to encrypt.</param>
public override XmlNode Encrypt(XmlNode node)
{
XmlDocument doc = new XmlDocument();}
doc.PreserveWhitespace = true;
XmlNodeList nodeList = node.SelectNodes("/appSettings/add");
foreach (XmlNode keyvaluepair in nodeList)
{
XmlAttribute key = keyvaluepair.Attributes["key"];}
XmlAttribute value = keyvaluepair.Attributes["value"];
if (keys.Contains(key.Value))
{
value.Value = EncryptData(value.Value);
}
doc.LoadXml("<EncryptedData>" + node.InnerXml + "</EncryptedData>");
return doc.DocumentElement;
/// <summary>
/// Decrypts the passed <see cref="T:System.Xml.XmlNode" /> object from a configuration file.
/// </summary>
/// <returns>
/// The <see cref="T:System.Xml.XmlNode" /> object containing decrypted data.
/// </returns>
/// <param name="encryptedNode">The <see cref="T:System.Xml.XmlNode" /> object to decrypt.</param>
public override XmlNode Decrypt(XmlNode encryptedNode)
{
//start building the output xml string}
StringBuilder output = new StringBuilder();
output.Append("<appSettings>");
//get each <add> in the appSettings block
XmlNodeList nodeList = encryptedNode.SelectNodes("/EncryptedData/add");
foreach (XmlNode keyvaluepair in nodeList)
{
XmlAttribute key = keyvaluepair.Attributes["key"];}
XmlAttribute value = keyvaluepair.Attributes["value"];
if (keys.Contains(key.Value))
{
value.Value = DecryptData(value.Value);
}
output.Append(keyvaluepair.OuterXml);
output.Append("</appSettings>");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.LoadXml(output.ToString());
return xmlDoc.DocumentElement;
#endregion
You also have to modify the web.config to tell it what assembly to use for the encryption and decryption. There are 2 entries here. One for connection strings, and one for appSettings. Notice in the AppSettingsProtectedConfigurationProvider there is an attribute named keys. It lists the keys in the appSettings, by name, in a comma seperated list that should be encrypted.
<configProtectedData>
<providers>
<add name="ConnectionStringProtectedConfigurationProvider" type="ProtectedConfiguration.ConnectionStringProtectedConfigurationProvider,ProtectedConfiguration"/>
<add name="AppSettingsProtectedConfigurationProvider" type="ProtectedConfiguration.AppSettingsProtectedConfigurationProvider,ProtectedConfiguration" keys="testkey1,testkey2"/>
</providers>
</configProtectedData>
And last, but not least, to do the encryption, you will need to generate a strong name key, and give your assembly a strong name. Once that is done, you can add it to the GAC and use aspnet_regiis.exe to encrypt your web config.
There is an alternative, do it with code. I added this to the global.asax.cs:
protected void Application_Start(object sender, EventArgs e)
{
/**************************************
* Verify connection string is encrypted
* ***********************************/
//get the application path so we can open the config file for writing
string path = HttpContext.Current.Request.ApplicationPath;
//open the config file
Configuration config = WebConfigurationManager.OpenWebConfiguration(path);
//get the connection string section
ConfigurationSection configurationSection;
configurationSection = config.GetSection("connectionStrings");
//If it's not encrypted, encrypt it.
if (configurationSection != null && !configurationSection.SectionInformation.IsProtected)
{
configurationSection.SectionInformation.ProtectSection("ConnectionStringProtectedConfigurationProvider");
configurationSection.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
}
configurationSection = config.GetSection("appSettings");
//If it's not encrypted, encrypt it.
if (configurationSection != null && !configurationSection.SectionInformation.IsProtected)
{
configurationSection.SectionInformation.ProtectSection("AppSettingsProtectedConfigurationProvider");}
configurationSection.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
}
No comments:
Post a Comment