views:

420

answers:

3

I have an application that connects to a number of SQL Servers for monitoring and other tasks. Currently, I only support trusted authentication, since I don't have to store anything sensitive. I want to add the ability to use SQL Authentication (username/password).

What's the best approach to storing this sensitive data between sessions? Is there a user-only certificate or encryption key available to me that I can use? Is it safe enough to use a registry key that's generated randomly per-user to encrypt this information? If there's a way I can use a key (or create one and store it) in such a way that no other user on the computer can access it, that's ideal.

I understand encryption, so I'm not looking for a tutorial - I'm looking for the safest method to keep one user's configuration data for my application safe from other users.

+6  A: 

Use Data Protection API (aka. DPAPI). Each use has a key that is protected by its password. You store the password/connection string in a the .config file and .Net framework has methods to encrypt/decrypt a config section with a machine key, an user key or an RSA key. Don't reinvent the wheel doing your own custom scheme. And using a registry key is definitely a bad idea. Security comes from secrets, not from access protecttion: it must rely on the user providing a secret (its password at login time), not on a unnaccessible registry key.

How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI

Remus Rusanu
It appears that I can use the DPAPI, and store a key in the user store, so it's not accessible to any other users on the machine. This looks to be exactly what I want to do - thanks!
rwmnau
I ended up using a modified version of the same here: http://www.obviex.com/samples/dpapi.aspx
rwmnau
A: 

As Remus pointed out, there is user/machine level encryption available.

i'm generally terrified of such things because that data can be lost relativly easily.

If i were you i'd roll my own system. Encrypt the connection strings using a key hard-coded in your application, convert to Base64, and store the resulting, encrypted, connection strings in the registry.


The following function takes a string, encrypts it with AES-256 and the specified key, and base64's the result so it's back as a printable string:

Sample Usage:

String connectionString = EncryptString(
     "Provider=SQLOLEDB;Data Source=Mango;User Id=sa;Password=hello",
     "A fairly complicated password, like a guid: 8B4B0D73-84C9-4A1E-8DD2-9A189F84FD9B");


public static string EncryptString(string source, string key)
{
    Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(key, salt);
    byte[] derivedKey = deriveBytes.GetBytes(derivedKeySize);


    Rijndael rijndael = Rijndael.Create();

    rijndael.Mode = cipherMode;
    rijndael.Padding = paddingMode;
    rijndael.KeySize = keySize;
    rijndael.BlockSize = blockSize;
    rijndael.FeedbackSize = blockSize; // no bigger than the blocksize

    rijndael.Key = derivedKey;
    rijndael.IV = iv;

    ICryptoTransform transform = rijndael.CreateEncryptor();

    byte[] encoded = Encoding.UTF8.GetBytes(source);

    byte[] target = transform.TransformFinalBlock(encoded, 0, encoded.Length);
    return Convert.ToBase64String(target);
}

public static string DecryptString(string source, string key)
{
    Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(key, salt);
    byte[] derivedKey = deriveBytes.GetBytes(derivedKeySize);

    Rijndael rijndael = Rijndael.Create();

    rijndael.Mode = cipherMode;
    rijndael.Padding = paddingMode;
    rijndael.KeySize = keySize;
    rijndael.BlockSize = blockSize;
    rijndael.FeedbackSize = blockSize; // no bigger than the blocksize

    rijndael.Key = derivedKey;
    rijndael.IV = iv;

    ICryptoTransform transform = rijndael.CreateDecryptor();

    byte[] decoded = Convert.FromBase64String(source);
    byte[] target = transform.TransformFinalBlock(decoded, 0, decoded.Length);

    return Encoding.UTF8.GetString(target);
}

    private static readonly byte[] iv = {
        0x30,0xA6,0x65,0xDE,0x8C,0x63,0x17,0x44,
        0xB6,0xFD,0xEA,0x5F,0x76,0xA1,0x1C,0x5F
    };

    private static readonly byte[] salt = {
        0xF9,0x39,0x0C,0xE0,0x22,0xE0,0x8E,0x84,
        0xB2,0x05,0x1E,0xA8,0x6D,0x1C,0x39,0xAC
    };

    private const int keySize = 256;
    private const int blockSize = 128;
    private const CipherMode cipherMode = CipherMode.CBC;
    private const PaddingMode paddingMode = PaddingMode.PKCS7;
    private const int derivedKeySize = 32;
Ian Boyd
+1  A: 

As Remus said, use DPAPI. But instead of using PInvoke methods (as in your linked example), use the ProtectedData class. That is a managed wrapper around DPAPI. A lot of the examples use PInvoke to access DPAPI because there was no managed way to do it before .Net 2.0. The DataProtectionScope class enables you to encrypt/decrypt data for the current user or machine.

adrianbanks
Noted - I dislike the invokes as well, and it's nice to know that there's a managed wrapper around it.
rwmnau