Encrypt and Hide Sensitive Global Configuration Data

By Peter A. Bromberg, Ph.D.
To "Print This Page" Link

Peter Bromberg

One of the biggest concerns of .NET Developers (and their clients) is protecting sensitive configuration data such as usernames and passwords, database connection strings and other global data that needs to be made easily available application - wide. Of course, web.config doesn't normally get served by IIS, provided the default mappings are set in machine.config and not overriden in a local web.config file. But a lot of people I"ve talked to don't feel comfortable having sensitive data in web.config. So of course, the first step would be that we can encrypt the data for starters.



But how to make the data you need available from within a class library running in the same App Domain, not just from within your web pages? DLL's can't read configuration data, and they certainly can't read web.config without writing a custom class to physically load the web.config or another xml configuration document and do some serious XPATH. That's overkill, since web.config is already loaded automatically when the application first starts, and the data in it is cached.

There are a number of solutions to this problem, varying in complexity and speed. My personal philosophy is that if a hacker can somehow get to read your web.config file, he's just as likely at that point to be able to execute arbitrary code of his choice so its already "too late". Unfortunately, lots of people don't fully understand the Windows/IIS/.NET security model and because of their naivete, they naturally decide that they want "overkill" when security comes into the fray. The result can be an extraordinary amount of additional coding, and a potential slowdown in the application's performance.

In my recent article on Creating an XML-Safe DES Encryption Class, I detail how to create a class library that can encrypt any string of data and Base64 encode the result, making it safe to store in an XML element without fear of having illegal characters in the stored string. Here I will present a reasonably elegant solution to the configuration data problem - one that keeps your data encrypted, makes it available instantly, and - above all - makes data of almost any type available from memory, both from your web pages and from your related assemblies.

The key to the technique is based on the expectation that all your DLL's will be called from some process that originated with a web user and therefore will be running in the same App Domain that the web application created when the first request came in. The System.AppDomain base class is one that is worthy of considerable study.

Application domains, which are represented by AppDomain objects, provide isolation, unloading, and security boundaries for executing managed code.

Multiple application domains can run in a single process; however, there is not a one-to-one correlation between application domains and threads. Several threads can belong to a single application domain, and while a given thread is not confined to a single application domain, at any given time, a thread executes in a single application domain.

Among many other features, AppDomain offers a CurrentDomain property that exposes several interesting methods, two of which are GetData() and SetData().

AppDomain exposes a set of predefined items:

Value of 'name' Property
"APPBASE" ApplicationBase
"APP_CONFIG_FILE" ConfigurationFile
"DYNAMIC_BASE" DynamicBase
"DEV_PATH" (no property)
"APP_NAME" ApplicationName
"CACHE_BASE" PrivateBinPath
"BINPATH_PROBE_ONLY" PrivateBinPathProbe
"SHADOW_COPY_DIRS" ShadowCopyDirectories
"FORCE_CACHE_INSTALL" ShadowCopyFiles
"CACHE_BASE" CachePath
(application specific) LicenseFile

However, in our case we are more interested in the fact that the GetData and SetData methods allow us to store any data we want - objects, types, anything that supports ISerializable. Not only that, but by simply issuing the call:

System.AppDomain.CurrentDomain.GetData("connectionstring")

- we can pull our data out from anywhere in the AppDomain - whether it's the code in an ASPX or ASMX page or a custom class library we've called in our code.

So the key to the whole scheme would now go as follows (you most likely have it already figured out, if you've been with me):

1) Encrypt the stuff we need and store it in web.config in the appSettings section, which allows us to use ConfigurationSettings.AppSettings("ABCD") to easily retrieve our data.
2) After reading the data out in Global.asax in the Sub Application_Start, we decrypt it using our secret key, and store it immediately with name, value pairs in the System.AppDomain   cache.
3) Anytime we need the data, we simply call System.AppDomain.CurrentDomain.GetData("ABCD") and we're done.

Here is a sample of some custom keys I've stored in the appSettings section of my web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- Encrypt your data using a key that you remember and place encrypted data in new keys in this section -->
<add key="ABCD" value="Ni/6UHcq+3r5eEerZ00Kbbd4WDzaH1zx" />
<add key="EFGH" value="1CejuTBT3xd6PXj3v2pIvtE0oDTDKcFH" />
<add key="IJKL" value="P0FjG5sL5k19aTsI1hHULEVqbU0Tq2SthExjOL
wIWfGxDfoe83B4POdjEJ/deRG3+z0J0lEtNAGLWKIBjzKoFlr1FQ2EekcWq79NaFVLZ5nbMOuHSwwKgYxqXOkA6GNw" />
</appSettings>
<system.web>

the Application_Start code in global.asax would look like so:

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Retrieve Global app settings from Web.config, decrypt, and put in
' AppDomain cache where they are available to all classes in the AppDomain...
Dim fe As Encryption64 = New Encryption64()
Dim ABCD As String = fe.DecryptFromBase64String(ConfigurationSettings.AppSettings("ABCD"), "12345678")
System.AppDomain.CurrentDomain.SetData("ABCD", ABCD)
Dim EFGH As String = fe.DecryptFromBase64String(ConfigurationSettings.AppSettings("EFGH"), "12345678")
System.AppDomain.CurrentDomain.SetData("EFGH", EFGH)
Dim IJKL As String = fe.DecryptFromBase64String(ConfigurationSettings.AppSettings("IJKL"), "12345678")
System.AppDomain.CurrentDomain.SetData("IJKL", IJKL)
fe = Nothing
End Sub

The call you see to "fe" as Encryption64 is a call to my encryption class. The "12345678" you see as the last method parameter is the "Secret key"
that is used to both encrypt and decrypt the data. In the event that you decide you don't want your decryption key exposed in global.asax, you could simply make a call to a small assembly whose sole purpose is to return your decryption key. The key value inside the assembly would be encrypted itself, and part of the function that returns it would be to decrypt it first. There's a copy of the .NET dll in the code download for this article; if you want to see the source, visit the article I mentioned above.

The test web page that is included in the download has simple code as follows:

Response.Write("Encrypted global data:<BR>")
Response.Write("ABCD =" & System.AppDomain.CurrentDomain.GetData("ABCD") & "<BR>")
Response.Write("EFGH =" & System.AppDomain.CurrentDomain.GetData("EFGH") & "<BR>")
Response.Write("IJKL =" & System.AppDomain.CurrentDomain.GetData("IJKL") & "<BR>")

And the result you'll see when you run the page is:

Encrypted global data:
ABCD =PETER\PrivilegedUser
EFGH =PrivilegedPassword
IJKL =Provider=SQLOLEDB.1;Data Source=YourServer;Initial Catalog=YourDatabase;Trusted Connection=Yes

You now have in your hands a simple, very fast, and extensible method for protecting private data and making it available to your components from an App-Domain - wide in-memory cache.


Download the code that accompanies this article


 

Peter Bromberg is an independent consultant specializing in distributed .NET solutions Inc. in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com