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

32 Comments

  • You should clear out the encryptedData and decryptedData byte arrays once you are done with them using Array.Clear(..).

    Raj

  • The salt is presumably worthless, given the existence of Reflector. I suppose is has some worth, in that it adds a small layer of extra obfuscation.

    And if a bad guy can get at the config file to read it, I wonder how hard it would be to run the DPAPI to decrypt it...

    Is this more than security through obscurity? Genuine question to any security experts out there.

  • Is this more than security through obscurity? Genuine question to any security experts out there.

    Good question

  • IMO, this *is* security through obscurity. In addition one would use a key generation logic for the entropy (instead of a string) and use a code obfuscator to add an additional layer of obscurity.

    Raj

  • If the badguy has access to your Windows account then this kind of protection is useless.
    This is more of a protection against someone reading your config from another Windows account. (as far as i can tell)

    Also, the additional entropy feels more like an IV then a salt?

  • @commenter I agree that the salt adds little to the security of the system, since it's required to the same on encryption and decryption. I'd still use it, since it would provide another level of protection over automated attacks.

  • Is someone had your system password, I think it wouldn't be to hard for him to get authenticated access to your filesystem :)

  • Looks a nice solutions.

    If only there were some way to do this on our legacy Solaris boxes.

  • woow! dude, thats it a great post thanks for it

  • Cool, but you should note that the project may require adding a .NET reference to System.Security.dll before the enums (such as DataProtectionScope) will resolve correctly.

  • Very nice this is something I wanted to do with my app.config.

    I did notice that you can't read the SecureString on decrypt?

    SecureString password = DecryptString(AppSettings.Password)

    I did find so info on Marshal.SecureStringToBSTR which will convert it back to regular string after decryption?

    Is this a good method or is there another way to decrypt back to a readable string?

    Thanks

  • I'm pretty new to VB.NET and I'm working on a desktop application. I need to encrypt some items in the userSettings and applicationSettings sections of the app.config file.

    I successfully converted the example code to VB, but I have a question about implementing the code: Where do I put it? I have one form in my app. Will it just go in the code behind for the form, or does the code need to go in its own class? And if that's the case, how do I access it from my form?

    If not the author, could SOMEone please help me?

    Thanks!

  • Use appropriate members of the System.Runtime.InteropServices..::.Marshal class, such as the SecureStringToBSTR method, to manipulate the value of a SecureString object.

  • You can see he already does that in the ToInsecureString() method. To get the readable string do this: string readable = ToInsecureString(password);

  • Just two small issues if it's not information set at runtime from the user such as a password, but pre-coded or preset value in the project's applications setting, such as connection strings:

    - this secures the information in the app.config file, but, if you open the .exe file with notepad, the value will still be obvious in plain text.

    - if the application is installed on the users machine, but not yet run, the information isn't encrypted yet and vulnerable.

  • Informative post, but:
    SecureString implements IDisposable... I guess, it is recommended to call it's Dispose Method when you're done with it... so imo...

    using(var secure = new SecureString()) {
    ...
    }

    ...should be the way to go.

  • I know it is unlikely that this old post is still monitored, but in case ...

    1) Can this technique be used if the configuration file is to be encrypted and decrypted by multiple users on the same machine? We have a common config file for an app we develop but one that gets used by 4 seperate accounts.

    2) Can this technique be used at all if the configuration file is a non-standard configuration file? We use our own for legacy reasons.

  • Cheers for this snippet - something nice and straight forward for my application. Something to stop the casual browser from nicking the password and/or any other config stuff I don't want.

    No point to this point other than: thanks - it helped me out!

  • I'm experiencing the same issue as Tomas... the DecryptString returns an empty value on the customer's test server, but works fine on my dev machine. Odd...

  • references required:

    using System.Security;
    using System.Security.Cryptography;

  • Thanks for a great explanation and code.

  • very good, i made a class from your code and using it in all my projects where i need to encrypt the passwords

  • The Decrypt function needs a User Profile loaded to store the keys. So my implementation failed as i was decrypting using the SYSTEM context on a server. Is there any level of protection like this where you don;t need the user profile loaded to decrypt?

  • I need to run my exe from a common network directory and several users will access it from the same network directory.

    Is it possible to use a commom key so taht it works for all user when the executable is in network directory with the app.config file but user loged in from there machine

  • "When you encrypt something on machine A it's NOT possible to copy the project to machine B and decrypt it.

    System.Security.Cryptography.DataProtectionScope.CurrentUser

    Is the hint. It is possible to do it for different users on the same machine with

    System.Security.Cryptography.DataProtectionScope.LocalMachine

    but not for different machines.
    "

    This is BS.. How can I store a password encrypted in the app.config, but carry around the key in the source to decrypt it. It has to be portable accross machines...?

  • Gavin,
    Read the DPAPI docs. THe key is not required in the code. In fact the key is not stored at all. The random data used to generate the key is stored in a protected file in teh users' home folder

  • The code was a big help for me. Thanks.

  • The decryption for me was actually:

    string password = ToInsecureString(DecryptString(Properties.Settings.Default.EncryptedPassword));

  • Great Article. The "Encrypting Strings with ProtectedData" worked magnificiently in my code. Thanks a ton.

  • Great articale!!!!

  • This is working fine but it cannot be used if you encrpyt the password and you want to use the password over several machines with different users. Unfortunately there seems only the option to encrypt with CurrentUser or LocalMachine. If you want to decrypt with another machine or user it will not work.

  • Pretty nice post. I just stumbled upon your blog and wanted to mention that I've really enjoyed browsing your blog posts. After all I'll be subscribing for your feed and
    I hope you write once more soon!

Comments have been disabled for this content.