I'm building a C# .NET database app and I was not happy with the default behavior of Visual Studio's settings system. By default it places database connection strings in the app.config under the application scope. This leaves your final build with an XML file that contains plain text of your connection string including your password if you chose to store it.
Due to the purpose of my application, I wanted to have the server info editable and easily accessable, but also secure. My initial thought was to just encrypt the connection string in the app.config, but this turned out to be obnoxious because of the constraints of .NET. Specifically, application scoped settings are read-only at runtime.
I chose to store the connection string as a user scoped string setting. This way I could setup event handlers to encrypt and decrypt the string between the disk and the database calls without requiring any changes to the rest of my code.
On the settings GUI within Visual Studio, click on view code, bringing up Settings.cs where you can add to the settings class. Here is the code I used to intercept disk reads and writes of the config file to encrypt/decrypt as needed.
public Settings() {
// The SettingsSaving event is raised before the setting values are saved.
this.SettingsSaving += this.SettingsSavingEventHandler;
// The SettingsLoaded event is raised after the setting values are loaded.
this.SettingsLoaded +=new System.Configuration.SettingsLoadedEventHandler(Settings_SettingsLoaded);
}
// Settings were just loaded, decrypt them.
private void Settings_SettingsLoaded(object sender, System.Configuration.SettingsLoadedEventArgs e)
{
string tempConString = this.EncryptedConnectionString;
try
{
tempConString = SimpleCryptoEngine.Decrypt(tempConString);
}
catch (System.Exception) { }
// Not that elegant, but makes sure that what was decrypted is in the expected format.
// this allows defaults to be set in plain text if you like. If nothing else it allowed
// the app to load the defaults I want and then save it encrypted so I could replace the
// plain text with the saved enrypted version.
if (tempConString.Substring(0, 6) == "server")
{
this.EncryptedConnectionString = tempConString;
}
}
// Settings are going to be saved, encrypt the string before it's written
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e)
{
this.EncryptedConnectionString = SimpleCryptoEngine.Encrypt(this.EncryptedConnectionString);
}
My SimpleCryptoEngine class is below. This allows for a simple key encryption that works across machines unlike many of the built-in .NET encryption schemes that are based on machine keys.
class SimpleCryptoEngine
{
public static string Encrypt(string toEncrypt)
{
byte[] keyArray;
byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);
//Set the key.
string key = "Henge Consulting LLC";
MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
hashmd5.Clear();
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
tdes.Key = keyArray;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
tdes.Clear();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
public static string Decrypt(string cipherString)
{
byte[] keyArray;
byte[] toEncryptArray = Convert.FromBase64String(cipherString);
// Set the same key used in encrypt
string key = "Henge Consulting LLC";
MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
hashmd5.Clear();
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
tdes.Key = keyArray;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
tdes.Clear();
return UTF8Encoding.UTF8.GetString(resultArray);
}
}
Quick note: after editing the connection string you must call Properties.Settings.Default.Save() to write the encrypted string to the config file. This leaves the encrypted string in memory so be sure to call Properties.Settings.Default.Reload() afterwards. This will require the system to trigger the Settings_Loaded event again decrypting the string, or just write it to disk, then replace it with the unencrypted string.
I hope this comes in handy for someone.
--Hunter
