6.6. Encrypt Secrets for the Current User
Applications often need a way to store private data in a file or in memory. The obvious solution is symmetric encryption, which scrambles your data using a random series of bytes called a secret key. The problem is that when you want to decrypt your scrambled data, you need to use the same secret key you used to encrypt. This introduces serious complications. Either you need to find a secure place to safeguard your secret key (which is tricky at best), or you need to derive the secret key from some other information, like a user-supplied password (which is much more insecure, and can break down entirely when users forget their passwords).
Note: Need a quick way to encrypt secret information, without needing to worry about key management? The long awaited solution appears in . NET 2.0 with the ProtectedData class.
The ideal solution is to have the Windows operating system encrypt the data for you. To accomplish this, you need the DPAPI (Data Protection API), which encrypts data using a symmetric key that's based on a piece of user-specific or machine-specific information. This way, you don't need to worry about key storage or authentication. Instead, the operating system authenticates the user when he logs in. Data stored by one user is automatically inaccessible to other users.
In previous versions of .NET, there were no managed classes for using the DPAPI. This oversight is corrected in .NET 2.0 with the new ProtectedData class in the System.Security.Cryptography namespace.
6.6.1. How do I do that?
The ProtectedData class provides two shared methods. ProtectData( ) takes a byte array with source data and returns a byte array with encrypted data. UnprotectData( ) performs the reverse operation, taking an encrypted byte array and returning a byte array with the decrypted data.
The only trick to using the ProtectData( ) and UnprotectData( ) methods is that you can only encrypt or decrypt data in a byte array. That means that if you want to encrypt strings, numbers, or something else, you need to write it to a byte array before you perform the encryption.
To see this in action, you can run the console application code in Example 6-6.
Example 6-6. Storing an encrypted string of text in a file
Imports System.Security.Cryptography Imports System.IO Module ProctedData Sub Main( ) ' Get the data. Console.WriteLine("Enter a secret message and press enter.") Console.Write(">") Dim Input As String = Console.ReadLine( ) Dim DataStream As MemoryStream If Input <> "" Then Dim Data( ), EncodedData( ) As Byte ' Write the data to a new MemoryStream. DataStream = New MemoryStream( ) Dim Writer As New StreamWriter(DataStream) Writer.Write(Input) Writer.Close( ) ' Convert the MemoryStream into a byte array, ' which is what you need to use the ProtectData( ) method. Data = DataStream.ToArray( ) ' Encrypt the byte array. EncodedData = ProtectedData.Protect(Data, Nothing, _ DataProtectionScope.CurrentUser) ' Store the encrypted data in a file. My.Computer.FileSystem.WriteAllBytes("c:\secret.bin", EncodedData, False) End If End Sub End Module
When you run this application, you'll be prompted to type in some text, which will be encrypted using your current user account information and stored in the file secret.bin. The data won't be accessible to any other user.
To verify that the data is encrypted, you have two choices. You can open the file and take a look for yourself, or you can modify the code so that it reads the data directly from the encrypted memory stream. This code tries the latter, and displays a string of meaningless gibberish as a result:
' Verify the data is encrypted by reading and displaying it ' without performing any decryption. DataStream = New MemoryStream(EncodedData) Dim Reader As New StreamReader(DataStream) Console.WriteLine("Encrypted data: " & Reader.ReadToEnd( )) Reader.Close( )
To decrypt the data, you need to place it into a byte array and then use the UnprotectData( ) method. To extract your data out of the unencrypted byte array, you can use a StreamReader. To add decryption support to the previous example, insert the following code, which opens the file and displays the secret message that you entered earlier:
If My.Computer.FileSystem.FileExists("c:\secret.bin") Then Dim Data( ), EncodedData( ) As Byte EncodedData = My.Computer.FileSystem.ReadAllBytes("c:\secret.bin") Data = ProtectedData.Unprotect(EncodedData, Nothing, _ DataProtectionScope.CurrentUser) Dim DataStream As New MemoryStream(Data) Dim Reader As New StreamReader(DataStream) Console.WriteLine("Decoded data from file: " & Reader.ReadToEnd( )) Reader.Close( ) End If
Remember, because the data is encrypted using the current user profile, you can decrypt the data at any time. The only restriction is that you need to be logged on under the same user account.
Note that when you protect data, you must choose one of the values from the DataProtectionScope enumeration. There are two choices:
Note: No matter which DataProtectionScope you choose, the encrypted information will be stored in a specially protected area of the Windows registry.
In the current example, user-specific data is stored. However, you could modify the DataProtectionScope to store data that's accessible to any user on the current computer.
6.6.2. What about...
...protecting data before you put it in a database? Once you use the ProtectedData class to encrypt your data, you can put it anywhere you want. The previous example wrote encrypted data to a file, but you can also write the binary data to a database record. To do so, you simply need a binary field in your table with enough room to accommodate the encrypted byte array. In SQL Server, you use the varbinary data type.