We have table where we keep user/password and some other data. Each record's password field must be encrypted. We decided to use AesCryptoServiceProvider for encryption and manually created rgbKey for one time. Then encrypted all the user passwords and inserted records into our table where password fields encrypted with our rgbKey.
We have several servers behind load balancer and each server should read these records and can decrypt password field value. At the beginning we placed rgbKey in base64StringFormat into our dll(API) and likewise all servers will be using this API and can decrypt encrypted values.
However, it is very insecure to keep rgbKey in the dll file. We discussed whether to keep our key in DPAPI. At the decryption time, at any our server, we will get our key from DPAPI and successfully decrypt encrypted value.
I thought that I can produce protected key for once in any of the server with current user mode, then save it in our common dll. Each server using common dll, giving this protected key(byte[]) to DPAPI with the same user, can get unprotected key value. However it works only in the machine where it is protected, at the other machines it gives the error : "Key not valid for use in specified state."
I give a sample code for my test console application:
First of all I run my PDPAPI() function in my local machine:
static void PDPAPI() {
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] rgbKey = encoding.GetBytes( "MyRgbKey" );
byte[] protectedData = ProtectedData.Protect( rgbKey, null, DataProtectionScope.CurrentUser );
string base64ProtectedData = Convert.ToBase64String( protectedData );
Console.WriteLine( "base64ProtectedData:{0}", base64ProtectedData );
}
Then hardcodedly add to my UDPAPI() function base64ProtectedData value got from the above execution, rebuild the solution :
static void UDPAPI() {
string base64ProtectedData = "****";
byte[] protectedData = Convert.FromBase64String( base64ProtectedData );
byte[] unProtectedData = ProtectedData.Unprotect( protectedData, null, DataProtectionScope.CurrentUser );
string base64unProtectedData = Convert.ToBase64String( unProtectedData );
Console.WriteLine( "base64unProtectedData:{0}", base64unProtectedData );
}
Now if I run UDPAPI() function in my local machine it successfully unprotect the data, however if I migrate my console application to any other server, and logging in that server with my network user, the console application gives error "Key not valid for use in specified state.".
Honestly for password encryption, you won't have the key storage issue if you use a one way hash and then instead of using the plaintext of the password, just hash user input and compare. The typical thing to use in C# is PBKDF2 (Rfc2898DeriveBytes). For other options see Crypto:What makes a hash function good for password hashing?
The key storage issue is always going to be difficult, DPAPI a pretty decent option it can protect the keys from other users on the same machine, and is much better than storing it in the DLL, but understand what it's doing, it's encrypting with the password hash of whatever windows user you are running (actually it's doing a lot more, key derivation, rotation etc from the password hash), and that's why you can't use the data encrypted from your local machine directly on the server. To use DPAPI, you will have to encrypt your key separately on each server (and that's not a bad thing).
It's always preferable not to hard code your key, not as much because it could be accessible, but because it's easier to change when you need to, or in different settings. Typically the most you can do without specialized hardware is just keep your key separate from your data. You can make things more difficult by encrypting the key, but you are just adding another key that an attacker would also have to get access to, there isn't an ideal solution.
If you want an easy way to be able to change your keys over time, I have ported the keyczar framework to c# it has the mechanisms needed to rotate keys so that you can still decrypt old data while encrypting new data, however it doesn't really offer anything more security wise in key storage accessibility.