'Where to store db passwords when using Windows .NET or ASP.NET applications

I have a scenario that has been troubling me for years. If you have to connect to a database or other service (like a web service) using a username and password, where would be the safest place to store this information if you are connecting through a .NET assembly? I understand that you would have to encrypt the password, but then you run into a kind of chicken-egg problem -- fine -- you can encrypt it, but then where do you put the key?

In .NET, you can't hard-code the password because you can decompile .NET code.

I looked at using assembly based rights with Isolated Storage, but MS recommends against storing unencrypted secret items there because privileged users can gain access, so again, we are moving the problem from point A to point B. So, for example, a domain admin with no need to know about the information in a database would be able to get access because of the ability to be an admin on any workstation on the domain.

You can encrypt the App. Config and Web.Config, but I believe privileged users can access the keys.

I think you run into the same problem with DPAPI.

I had considered storing the passwords, encrypted in a remote database, and getting them through OS authentication, but our department prohibits the storage of passwords on database servers. I am pretty sure I am stuck and wanted confirmation.



Solution 1:[1]

You don't want to store the password in the assembly, and reinventing the wheel only creates more trouble (and introduces more vulnerabilities) than it's worth. If you are using MS platform on both the database and web server, then the easiest way to handle this is use a trusted connection, and grant rights on the SQL server to the identity your application is using.

Second to that, I would just let DPAPI do its job to encrypt your connection settings.

Solution 2:[2]

You can use the following methods of the .NET Framework to protect your data; they use the DPAPI internally to protect your data, and you can directly use them in C# or VB.NET without having to fiddle around with system DLL calls:

namespace System.Security.Cryptography
{
    // Summary:
    //     Provides methods for protecting and unprotecting data. This class cannot
    //     be inherited.
    public sealed class ProtectedData
    {
        public static byte[] Protect(byte[] userData, 
            byte[] optionalEntropy, DataProtectionScope scope);
        public static byte[] Unprotect(byte[] encryptedData, 
            byte[] optionalEntropy, DataProtectionScope scope);
    }
}

To use it, add the reference System.Security to your project. I strongly recommend using the byte array optionalEntropy to add a SALT to your protected data (add some random values to the byte array which are unique for the data you intend to protect).

For scope you can use DataProtectionScope.CurrentUser, which will encrypt the data to protect with the current user's credentials.

In some scenarios, DataProtectionScope.LocalMachine is useful, as well. In this case, the protected data is associated with the machine context. With this setting, any process running on the computer can unprotect data. It is usually used in server-specific applications that run on a server where untrusted users are not allowed access.

Use the Protect method to encrypt the data, decrypt it with Unprotect. You may store the returned byte array according to the requirements of your application (file, database, registry, etc).

If you have to protect a string, you can use a helper method such as:

byte[] GetBytes(string text) => System.Text.Encoding.Unicode.GetBytes(text);

to convert it to a byte array.

Vice versa, if you unprotect the byte array, you can get the string back vie:

string GetString(byte[] text) => System.Text.Encoding.Unicode.GetString(text);

If your string is not Unicode, you can find more encodings in the System.Text.Encoding library of .NET.

Simple example (using the helper methods above together with the ProtectedData class):

using System.Security.Cryptography;
using static System.Security.Cryptography.ProtectedData;

void Main()
{
    const DataProtectionScope scope = DataProtectionScope.CurrentUser;
    byte[] salt = GetBytes("s5Dk9Fj2L12"); // change the salt
    
    // protect msg
    string msg = "Hello World!";
    var protMsg = Protect(GetBytes(msg), salt, scope);
    
    // get plaintext back
    string plainTxt = GetString(Unprotect(protMsg, salt, scope));
    Console.WriteLine(plainTxt);
}

More about the methods from DPAPI can be found here at MSDN:

For code samples and in case you are interested in encrypting parts of the application's .config file, check this out:

I recommend you to use a SALT (by using the optionalEntropy parameter) - it protects against rainbow table attacks.


There is one drawback of the DPAPI solution I'd like to mention: the key is generated based on your Windows credentials, which means whoever has access to your Windows credentials possibly has access to the protected data. A program running under your account can access the protected data as well.

Solution 3:[3]

This is good question and I've been looking for an answer myself. Problem I had was to keep db passwords secure in case server was hacked and individual files could be retrieved. One very interesting option I've found was that sections of web.config can be encrypted and decrypted automatically on the fly by .NET framework which would use Windows secure store to keep and retrieve encryption key for you. In my situation that was not available because my hosting provider was not supporting it but you may have a look at this option. Why I think it may work is that you can independently manage security of what users may access Windows secure store and significantly limit any potential breaches. A hacker who breaks into server might get a copy of your config files and all your assemblies but accessing decryption key would be another obstacle for him.

Solution 4:[4]

****UPDATED for Windows 10 1903+ ****

Microsoft has removed the dlls used by the previous method from WinMetadata and they are now not available even when doing the old manipulations. Instead, they now either propose to use a Target Framework Moniker or a Nuget package called Microsoft.Windows.SDK.Contracts that expose the modern windows APIs. [The official detail could be found here]. Note that for the nuget to install correctly, it is essential that the packet management is set to PackageReference instead of Packages.config elsewise, even though it could look like it is installed, it won't work.

If it is not already set to this manager, it is normally possible to convert the packages.config file to the new format (that is integrated in the project file) by right-clicking the packages.config file and choosing Migrate. If the Packages.config file does not yet exist, it is possible to set the default to use PackageReference by going in the Nuget Options. Note that the default setting varies between .Net FW, .Net Std and .Net Core. When I personally had to do it, weirdly, it didn't want to migrate the file. I had to completely uninstall my packages, delete the file, set the default and reinstall the packages. Something to do with TFVC if I remember well.

The APIs should then be available again.

**** ORIGINAL POST ****

If you are in a Windows only 8+ solution, you could also use the Windows Password Vault. Originally, this was built for Metro Apps but is also supported for Winform and WPF applications.

Basically, what you need is

  1. Add the following line in your project file inside the first property group
    <TargetPlatformVersion>8.0</TargetPlatformVersion>

  2. Reference Windows.Security.

    • Open the References Manager window
    • Select Windows tab
    • Select Core sub tab
    • Check Windows.Security.
  3. In the code, use (this is vb but C# is equivalent)

    Dim vault = New Windows.Security.Credentials.PasswordVault()
    vault.Add(New Windows.Security.Credentials.PasswordCredential(resource, userName, password))
    Dim cred = vault.Retrieve(resource, logon)
    cred.RetrievePassword()
    Dim pwd = cred.Password
    

Ref:

  1. https://docs.microsoft.com/fr-ca/archive/blogs/cdndevs/using-windows-8-winrt-apis-in-net-desktop-applications
  2. https://docs.microsoft.com/en-us/uwp/api/Windows.Security.Credentials.PasswordVault?view=winrt-19041

Solution 5:[5]

There are a couple of options here.

  1. Store them in the config file encrypted
  2. Store them in an external file that is encrypted with a generated seed. Obfuscate the code that stores this base seed or store it in a c++ dll (harded to decompile).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Maciej
Solution 4
Solution 5 tsells