In my opinion, the simplest way to encrypt sensitive data in .net is to use the Data Protection API (DPAPI) exposed through the System.Security.Cryptography.ProtectedData class. The ProtectedData class makes it quick and easy to encrypt and decrypt data using a user or machine specific key. For example, this is all it takes to encrypt and decrypt data using a user-specific key:
(You'll need to add a reference to System.Security.dll to your project for this to work.)
using System;
using System.Text;
using System.Security.Cryptography;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
string theSecret = "SuperSecret";
// Convert our input string into
// a byte array
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] plainTextData = encoding.GetBytes(theSecret);
// encrypt the string
byte[] encryptedData = ProtectedData.Protect(plainTextData, null, DataProtectionScope.CurrentUser);
// decrypt
byte[] descryptedData = ProtectedData.Unprotect(encryptedData, null, DataProtectionScope.CurrentUser);
string descryptedString = encoding.GetString(descryptedData);
}
}
}
There's a couple of drawbacks to this approach:
- Because we're using the Data Protection API (DPAPI) any other code running in the context of this user can decrypt this value. We can it a little more difficult to do this by passing in an Entropy argument.
- The encryption key is user specific. We can get around this limitation by using DataProtectionScope.LocalMachine instead. This will allow any user on the current machine to be able to decrypt our data. But what if you are developing a web application that will run in a web farm across multiple servers? What if you are building a component that will be deployed as part of three or four different applications that will all access some data in a common repository? The DPAPI does not expose any method for us to create a key and then export it for use on another machine.
Here's one way that we can get around this limitation:
- Open a command prompt and navigate to c:\windows\microsoft.net\framework\v2.0.50727
- Use the aspnet_regiis command line tool to create a new public/private key pair by running the following command: aspnet_regiis -pc "MyKeys" -exp
- The -pc switch instructs the aspnet_regiis tool to create a new RSA key pair in the machine keystore named "MyKeys".
- The -exp switch indicates that the key pair should be exportable.
- Next, we'll export the RSA key pair so we can use it on other machines later. Run aspnet_regiis -px "MyKeys" "c:\MyKeys.xml" -pri
- The -px switch tells aspnet_regiis to export the "MyKeys" key an RSA keypair to an Xml file (C:\MyKeys.xml).
- The -pri switch indicates that the private key should be included. Think about your scenario carefully. If you are only using this key on other machines to encrypt data, the private key is not needed. However, if you are going to encrypt and decrypt data, both the public and private keys will be needed.
- Copy the c:\MyKeys.xml file to the second machine.
- On the second machine, navigate to c:\windows\microsoft.net\framework\v2.0.50727 and run the following command: aspnet_regiis -pi "c:\MyKeys.xml"
- The -pi switch instructs aspnet_regiis to import our keyfile into the local machine keystore.
Now, for some code that makes use of all of this!
using System;
using System.Text;
using System.Security.Cryptography;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
string theSecret = "SuperSecret";
string encryptedString = EncryptString(theSecret);
string decryptedString = DecryptString(encryptedString);
}
public static string EncryptString(string plainTextString)
{
CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = "MyKeys";
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
{
// Use UnicodeEncoding to convert the plain text
// string to a byte array.
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] plainData = encoding.GetBytes(plainTextString);
// Encrypt the data
byte[] encryptedData = rsa.Encrypt(plainData, false);
// Convert the encrypted byte array to a base 64 string
// for safe storage
return Convert.ToBase64String(encryptedData);
}
}
public static string DecryptString(string encryptedBase64String)
{
CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = "MyKeys";
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
{
// Convert the encrypted Base 64 string back into a
// Byte Array.
byte[] encryptedData = Convert.FromBase64String(encryptedBase64String);
// Decrypt the Byte Array
byte[] decryptedData = rsa.Decrypt(encryptedData, false);
// Use UnicodeEncoding to convert the decrypted byte
// array back into a string
UnicodeEncoding encoding = new UnicodeEncoding();
return encoding.GetString(decryptedData);
}
}
}
}
There's a few things of note here:
CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = "MyKeys";
Notice that we're using the machine key store. If our application always runs as a particular user, we could have used the user store. In addition, our aspnet_regiis commands for creating, exporting, and importing keys could all have used use-specific key stores.
// Convert the encrypted byte array to a base 64 string
// for safe storage
return Convert.ToBase64String(encryptedData);
It's really important to convert your output to a Base64string rather than just trying to use UnicodeEncoding to convert your byte array to a string. The problems with this are explained well in this blog post.
Keep in mind that you would use a technique like this to store sensitive data or credentials that you need to decrypt. You would not want to employ this strategy for storing usernames and passwords for your application. In these scenarios, it's far better to use a one-way hashing algorithim and only store the salted hash of the password. This makes it much more difficult for a potential attacker to compromise your solution.
In addition, the RSACryptoServiceProvider used here is far slower than a symmetric algorithim like AES. If you were going to use this approach on any volume of data, you'd want to use this mechanism to encrypt and store a symmetric key. You'd then salt the symmetric key and use it to encrypt and decrypt your sensitive data.
