Encrypting Passwords in a .NET app.config File
I've been contributing to the Witty project lately. I'm a fan of Twitter, and it's nice to work on a popular WPF application with some hotshot coders including a WPF pro like Alan Le. Lately, I noticed that we were storing the user's password in plaintext application config file:
<setting name="Password" serializeAs="String"> <value>OOPS-WE-STORED-THE-PASSWORD-IN-PLAINTEXT</value> </setting>
So, yeah, that's less than ideal. Foolishly, I volunteered to fix it. There's plenty of information on encrypting ASP.NET configuration settings in web.config files, but encrypting settings in a desktop application isn't as well documented. Here's what I came up with.
DPAPI, Papi!
The best way to encrypt configuration settings is with DPAPI, the Data Protection Application Programmer's Interface:
This Data Protection API (DPAPI) is a pair of function calls that provide OS-level data protection services to user and system processes. By OS-level, we mean a service that is provided by the operating system itself and does not require any additional libraries. By data protection, we mean a service that provides confidentiality of data through encryption. Since the data protection is part of the OS, every application can now secure data without needing any specific cryptographic code other than the necessary function calls to DPAPI.
That sounds pretty good. But is it secure? Let's ask old man Wikipedia:
The keys used for encrypting the user's keys are stored under "%USERPROFILE%\Application Data\Microsoft\Protect\{SID}", where {SID} is the security identifier of that user. The DPAPI key is stored in the same file as the master key that protects the users private keys. It usually is 40 bytes of random data. DPAPI doesn't store any persistent data for itself; instead, it simply receives plaintext and returns cryptext (or vice-versa).
DPAPI security relies upon the system's ability to protect the Master Key and RSA private keys from compromise, which in most attack scenarios is most highly reliant on the security of the end user's credentials. Particular data binary large objects can be encrypted in a way that salt is added and/or an external user-provided password (aka "Strong Key Protection") is required. The use of a salt is a per-implementation option - i.e. under the control of the application developer - not controllable by the end user or IT professional.
Yeah, I didn't read it either. I did check the footnotes and saw that nobody's bragging about yoinking data out of it, though. And it has to be better than storing passwords in plaintext. So, awesome, let's go for it!
The Nuclear Option: Encrypt The Whole Thing
The easiest way to deal with the problem is to just encrypt the entire section. That's because the ConfigurationSection knows how to protect itself, like so:
protected override void OnStartup(StartupEventArgs e) { // Lots of other important stuff here... EncryptConfigSection("userSettings/Witty.Properties.Settings"); base.OnStartup(e); } private void EncryptConfigSection(string sectionKey) { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ConfigurationSection section = config.GetSection(sectionKey); if (section != null) { if (!section.SectionInformation.IsProtected) { if (!section.ElementInformation.IsLocked) { section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider"); section.SectionInformation.ForceSave = true; config.Save(ConfigurationSaveMode.Full); } } } }
Once we've done that, the entire settings section is encrypted and placed in a <CipherValue> block:
<userSettings> <Witty.Properties.Settings configProtectionProvider="DataProtectionConfigurationProvider"> <EncryptedData> <CipherData> <CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAABbLHX[...]</CipherValue> </CipherData> </EncryptedData> </Witty.Properties.Settings> </userSettings>
That's great from a security standpoint, but by encrypting everything, we've unnecessarily restricted access to all the information in the configuration file.
<userSettings> <Witty.Properties.Settings> <setting name="Username" serializeAs="String"> <value>UserNameGoesHere</value> </setting> <setting name="Password" serializeAs="String"> <value>OOPS-WE-STORED-THE-PASSWORD-IN-PLAINTEXT</value> </setting> <setting name="RefreshInterval" serializeAs="String"> <value>5</value> </setting> <setting name="LastUpdated" serializeAs="String"> <value /> </setting> <setting name="PlaySounds" serializeAs="String"> <value>True</value> </setting> ... </Witty.Properties.Settings> </userSettings>
So, what would be better is to encrypt just the password. To do that, we'll need to look into SecureString and System.Security.Cryptography.ProtectedData.
What's With The SecureString?
Like just about everything to do with ASP.NET development, David Hayden told us everything we need to know about SecureString years ago. The short story is that a System.String hangs around in memory until the garbage collector picks it up, so even if we're encrypting passwords or other sensitive data in our configuration file, it's possible to snag them from memory if we're using a standard System.String. SecureString uses our old friend DPAPI to encrypt values, so they're safe from memory snooping.
It's not as great as it sounds, though, because few API's accept or return SecureStrings. While it's a good practice to use SecureStrings when we can, we'll have to convert to and from standard System.String values at some point. While we're looking at security, we might as well use SecureStrings when possible, but we should keep in mind the fact that it's totally futile. Well, not that bad, but there are times where the sensitive information is still stored as insecure strings in memory.
Encrypting Strings with ProtectedData
So here's the actual meat of this post - the code I used to encrypt passwords in Witty's configuration. We've got two main methods, EncryptString and DecryptString. They both call in to ToSecureString and ToUnsecureString (great name, huh?) whose purpose should be pretty self-explanatory.
static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("Salt Is Not A Password"); public static string EncryptString(System.Security.SecureString input) { byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect( System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)), entropy, System.Security.Cryptography.DataProtectionScope.CurrentUser); return Convert.ToBase64String(encryptedData); } public static SecureString DecryptString(string encryptedData) { try { byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect( Convert.FromBase64String(encryptedData), entropy, System.Security.Cryptography.DataProtectionScope.CurrentUser); return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData)); } catch { return new SecureString(); } } public static SecureString ToSecureString(string input) { SecureString secure = new SecureString(); foreach (char c in input) { secure.AppendChar(c); } secure.MakeReadOnly(); return secure; } public static string ToInsecureString(SecureString input) { string returnValue = string.Empty; IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input); try { returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr); } finally { System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr); } return returnValue; }
Then we're pretty much set. When we want to encrypt passwords for storage, we'll make a call like this:
AppSettings.Password = EncryptString(ToSecureString(PasswordTextBox.Password));
And we can get the password back out with this kind of thing:
SecureString password = DecryptString(AppSettings.Password)
The payoff is that our configuration looks like this:
<Witty.Properties.Settings> <setting name="Username" serializeAs="String"> <value>jongalloway</value> </setting> <setting name="Password" serializeAs="String"> <value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAV[lots more stuff that's not my password]</value> </setting> <setting name="RefreshInterval" serializeAs="String"> <value>5</value> </setting> <setting name="LastUpdated" serializeAs="String"> <value>4/11/2008 12:10:33 AM</value> </setting> </Witty.Properties.Settings>
For further study:
http://msdn.microsoft.com/en-us/library/system.configuration.dpapiprotectedconfigurationprovider.aspx
http://www.codeproject.com/KB/security/ProtectedConfigWinApps.aspx
http://www.builderau.com.au/program/dotnet/soa/Encrypting-NET-configuration-files-through-code/0,339028399,339281837,00.htm