Ruminations of J.net idle rants and ramblings of a code monkey

Protecting Crypto Keys

.NET Stuff | Security

In my last post, I discussed how to work with symmetric encryption. One thing that I mentioned, but didn't go in to, was how to protect the keys for symmetric encryption. Here's the deal: you're using 256-bit Rijndael; you're doing everything right. But what do you do with the key? This is, after all, the key to your encrypted data (pun intended). If a bad guy gets the key, they'll be at your data in no time flat. This, but the way, is the excuse (and it is a poor excuse) that I've most commonly heard to defend a foray into craptology. But let's face it, it is a problem. What, oh what is a security conscious developer to do? Encrypt it with another symmetric algorithm? But then you have the same problem. How do we get ourselves out of this seemingly bottomless pit?
Fear not, dear developer. No reason to worry yourself about all of this mess. Since Windows 2000, the Data Protection API (DPAPI) has shipped with Windows, providing a clean solution to this problem. DPAPI is based on TripleDES (see the previous entry) ... but here's the deal. The TripleDES key is based on the Windows profile, is automatically rotated and the key itself exists in memory for only a brief period of time. But honestly, there's no need to worry about the details. It works, it works well and it's also been reviewed by external security experts and is generally considered to be an excellent implementation to solve this difficult problem.
Now, before we get to the code on how to use DPAPI, let's talk a little more about the details. First, DPAPI can be associated with a single user account or with the machine account. The user account mode is, in general, more secure; that's because when the machine account is used, anyone with access to the machine can get the data decrypted. But that doesn't mean you should jump right into using the user account mode. When you use the user account mode, you will need to load the user's profile (and desktop) in order to encrypt and decrypt. Now, you can technically do that in a web application (by way of some Win32 API calls via PInvoke), but that is a Very Bad Idea™. So ... user account mode is not good for web applications. It is, however, very good for desktop applications - especially in scenarios where there may be multiple users for the system. It's also good to use with Windows services. In both of these situations, the user profile and desktop is loaded and ready for you. One little thorn that you might run in to is this: you need to access the same encrypted data from multiple machines, but using the same user account. If you read the documentation, you'll see what appears to be a silver bullet to solve this problem ... roaming profiles. However, there be Dragons there. Big, nasty, fire-breathing dragons. Does it work? Yes ... in a perfect world. The problem is this: if the profile is unavailable, for whatever reason, Windows will quite happily create a temporary local profile. Which puts everything out of whack. Completely. (Don't ask how I know this ... I still have the scars.) For both modes, you can add an extra layer of security by adding entropy to the mix. It's just an extra bit of (again) gobbledygook added to the algorithm to ensure greater randomness. You'll see this in the code sample.
So, how to use it? In .Net 2.0 and higher, it's actually very easy. In .Net 1.x, you had to directly call the CryptoAPI via PInvoke. There was an implementation on MSDN that you could download and use, which was quite a relief. If you looked at the code, you'll be glad that you never had to write it yourself and your appreciation for crypto in .Net will increase 10 fold. The .Net 2.0 implementation is in (of course) the System.Security.Cryptography namespace, but is not in mscorlib.  It's in System.Security.dll, so if you don't see it, make sure you add it as a reference and all will be well. You have 2 classes in there related to DPAPI: ProtectedData and ProtectedMemory. Their names tell you the difference between them.
Here's a code sample of using DPAPI:
private string ProtectData(string clearText, string password)
{
//convert our clear text into a byte array.byte[] clearTextBytes = System.Text.Encoding.UTF8.GetBytes(clearText);//We're going to add some entropy to this.
//In this case, we're deriving random bytes from the password.
//This is a good way to use passwords in a more secure manner.
System.Security.Cryptography.PasswordDeriveBytes pwd =
new System.Security.Cryptography.PasswordDeriveBytes(password, null);byte[] entropy = pwd.GetBytes(16);//Do the encryption
//Notice that it is a static method.byte[] cipherText =
System.Security.Cryptography.ProtectedData.Protect(
clearText, entropy, System.Security.Cryptography.DataProtectionScope.CurrentUser);//write to the label.return Convert.ToBase64String(cipherText);
}
So ... not to hard, is it?  Now, before you go off encrypting your keys for your web.config files, I must mention one more little thing: ASP.Net 2.0 will actually encrypt sections of the web.config file for you as well as handle the encryption invisibly - you just continue to use the configuration API's like you always have. One way to do this is to use the aspnet_regiis command-line tool. You can read the docs on that on MSDN. More interesting to me, however, is the ability to do this in code. And, while the aspnet_regiis utility only works on web applications, doing this in code will work with every application. And so, without further ado, here's the code:
static public void EncryptConnectionStrings()
{
// Get the current configuration file.
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);// Get the section.
UrlsSection section =
(UrlsSection)config.GetSection("connectionStrings");// Protect (encrypt) the section.
section.SectionInformation.ProtectSection("DpapiProtectedConfigurationProvider");// Save the encrypted section.
section.SectionInformation.ForceSave = true;//And then save the config file.
config.Save(ConfigurationSaveMode.Full);
}