
Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
858 C H A P T E R 2 5 ■ C RY P TO G R A P H Y
Encrypting Sensitive Data in a Database
In this section, you will learn how to create a simple test page for encrypting information stored in a database table. This table will be connected to a user registered in the Membership Service. We suggest not creating a custom Membership provider with custom implementations of MembershipUser that support additional properties. As long as you stay loosely coupled with your own logic, you can use it with multiple Membership providers. In this sample, you will create a database table that stores additional information for a MembershipUser without creating a custom provider. It just connects to the MembershipUser through the ProviderUserKey—this means the actual primary key of the underlying data store. Therefore, you have to create a table on your SQL Server as follows:
CREATE DATABASE ExtendedUser GO
USE ExtendedUser GO
CREATE TABLE ShopInfo
(
UserId UNIQUEIDENTIFIER PRIMARY KEY,
CreditCard VARBINARY(60),
Street VARCHAR(80),
ZipCode VARCHAR(6), City VARCHAR(60)
)
The primary key, UserId, will contain the same key as the MembershipUser for which this information is created. That’s the only connection to the underlying Membership Service. As mentioned, the advantage of not creating a custom provider for just these additional fields is that you can use it for other providers. We suggest creating custom providers only for supporting additional types of data stores for the Membership Service. The sensitive information is the CreditCard field, which now is not stored as VARCHAR but as VARBINARY instead. Now you can create a page that looks like this:
<form id="form1" runat="server"> <div>
<asp:LoginView runat="server" ID="MainLoginView"> <AnonymousTemplate>
<asp:Login ID="MainLogin" runat="server" /> </AnonymousTemplate>
<LoggedInTemplate>
Credit Card: <asp:TextBox ID="CreditCardText" runat="server" /><br /> Street: <asp:TextBox ID="StreetText" runat="server" /><br />
Zip Code: <asp:TextBox ID="ZipCodeText" runat="server" /><br /> City: <asp:TextBox ID="CityText" runat="server" /><br /> <asp:Button runat="server" ID="LoadCommand" Text="Load"
OnClick="LoadCommand_Click" /> <asp:Button runat="server" ID="SaveCommand" Text="Save"
OnClick="SaveCommand_Click" /> </LoggedInTemplate>
</asp:LoginView>
</div>
</form>
The page includes a LoginView control to display the Login control for anonymous users and display some text fields for the information introduced with the CREATE TABLE statement. Within the Load button’s Click event handler, you will write code for retrieving and decrypting information

C H A P T E R 2 5 ■ C RY P TO G R A P H Y |
859 |
from the database, and within the Save button’s Click event handler, you will obviously do the opposite. Before doing that, though, don’t forget to configure the connection string appropriately.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <connectionStrings>
<add name="DemoSql"
connectionString="data source=(local); Integrated Security=SSPI; initial catalog=ExtendedUser"/>
</connectionStrings>
<system.web>
<authentication mode="Forms" /> </system.web>
</configuration>
Now you should use the ASP.NET WAT to create a couple of users in your membership store. After you have done that, you can start writing the actual code for reading and writing data to the database. The code doesn’t include anything special. It just uses the previously created encryption utility class for encrypting the data before updating the database and decrypting the data stored on the database.
Let’s take a look at the update method first:
protected void SaveCommand_Click(object sender, EventArgs e)
{
DemoDb.Open();
try
{
string SqlText = "UPDATE ShopInfo " +
"SET Street=@street, ZipCode=@zip, " + "City=@city, CreditCard=@card " +
"WHERE UserId=@key";
SqlCommand Cmd = new SqlCommand(SqlText, DemoDb);
//Add simple values Cmd.Parameters.AddWithValue("@street", StreetText.Text); Cmd.Parameters.AddWithValue("@zip", ZipCodeText.Text); Cmd.Parameters.AddWithValue("@city", CityText.Text); Cmd.Parameters.AddWithValue("@key",
Membership.GetUser().ProviderUserKey);
//Now add the encrypted value
byte[] EncryptedData = SymmetricEncryptionUtility.EncryptData(
CreditCardText.Text, EncryptionKeyFile); Cmd.Parameters.AddWithValue("@card", EncryptedData);
// Execute the command
int results = Cmd.ExecuteNonQuery(); if (results == 0)
{
Cmd.CommandText = "INSERT INTO ShopInfo VALUES" + "(@key, @card, @street, @zip, @city)";
Cmd.ExecuteNonQuery();
}

860 C H A P T E R 2 5 ■ C RY P TO G R A P H Y
}
finally
{
DemoDb.Close();
}
}
The two key parts of the previous code are the part that retrieves the ProviderUserKey from the currently logged-on MembershipUser for connecting the information to a membership user and the position where the credit card information is encrypted through the previously created encryption utility class. Only the encrypted byte array is passed as a parameter to the SQL command. Therefore, the data is stored encrypted in the database.
The opposite of this function, reading data, looks quite similar, as shown here:
protected void LoadCommand_Click(object sender, EventArgs e)
{
DemoDb.Open();
try
{
string SqlText = "SELECT * FROM ShopInfo WHERE UserId=@key"; SqlCommand Cmd = new SqlCommand(SqlText, DemoDb); Cmd.Parameters.AddWithValue("@key",
Membership.GetUser().ProviderUserKey); using (SqlDataReader Reader = Cmd.ExecuteReader())
{
if (Reader.Read())
{
// Cleartext Data
StreetText.Text = Reader["City"].ToString(); ZipCodeText.Text = Reader["ZipCode"].ToString(); CityText.Text = Reader["City"].ToString();
// Encrypted Data
byte[] SecretCard = (byte[])Reader["CreditCard"]; CreditCardText.Text =
SymmetricEncryptionUtility.DecryptData( SecretCard, EncryptionKeyFile);
}
}
}
finally
{
DemoDb.Close();
}
}
Again, the function uses the currently logged-on MembershipUser’s ProviderUserKey property for retrieving the information. If successfully retrieved, it reads the clear-text data and then retrieves the encrypted bytes from the database table. These bytes are then decrypted and displayed in the credit card text box. You can see the results in Figure 25-8.

C H A P T E R 2 5 ■ C RY P TO G R A P H Y |
861 |
Figure 25-8. Encrypting sensitive information on the database
Encrypting the Query String
In this book, you’ve seen several examples in which ASP.NET security works behind the scenes to protect your data. For example, in Chapter 20 you learned how ASP.NET uses encryption and hash codes to ensure that the data in the form cookie is always protected. You have also learned how you can use the same tools to protect view state. Unfortunately, ASP.NET doesn’t provide a similar way to enable automatic encryption for the query string (which is the extra bit of information you add to URLs to transmit information from one page to another). In many cases, the URL query information corresponds to user-supplied data, and it doesn’t matter whether the user can see or modify it. In other cases, however, the query string contains information that should remain hidden from the user. In this case, the only option is to switch to another form of state management (which may have other limitations) or devise a system to encrypt the query string.
In the next example, you’ll see a simple way to tighten security by scrambling data before you place it in the query string. Once again, you can rely on the cryptography classes provided with
.NET. In fact, you can leverage the DPAPI. (Of course, you can do this only if you are not in a server farm environment. In that case, you could use the previously created encryption classes and deploy the same key file to any machine in the server farm.)

862 C H A P T E R 2 5 ■ C RY P TO G R A P H Y
Wrapping the Query String
The starting point is to build an EncryptedQueryString class. This class should accept a collection of string-based information (just like the query string) and allow you to retrieve it in another page. Behind the scenes, the EncryptedQueryString class needs to encrypt the data before it’s placed in the query string and decrypt it seamlessly on the way out.
Here’s the starting point for the EncryptedQueryString class you need:
public class EncryptedQueryString : System.Collections.Specialized.StringDictionary
{
public EncryptedQueryString()
{
// Nothing to do here
}
public EncryptedQueryString(string encryptedData)
{
//Decrypt information and add to
//the dictionary
}
public override string ToString()
{
//Encrypt information and return as
//HEX-encoded string
}
}
You should notice one detail immediately about the EncryptedQueryString class: it derives from the StringDictionary class, which represents a collection of strings indexed by strings. By deriving from StringDictionary, you gain the ability to use the EncryptedQueryString like an ordinary string collection. As a result, you can add information to the EncryptedQueryString in the same way you add information to the Request.QueryString collection. Here’s an example:
encryptedQueryString["value1"] = "Sample Value";
Best of all, you get this functionality for free, without needing to write any additional code. So, with just this rudimentary class, you have the ability to store a collection of name/value strings. But how do you actually place this information into the query string? The EncryptedQueryString class provides a ToString() method that examines all the collection data and combines it in a single encrypted string.
First, the EncryptedQueryString class needs to combine the separate collection values into a delimited string so that it’s easy to split the string back into a collection on the destination page. In this case, the ToString() method uses the conventions of the query string, separating each value from the name with an equal sign (=) and separating each subsequent name/value pair with the ampersand (&). However, for this to work, you need to make sure the names and values of the actual item in the collection don’t include these special characters. To solve this problem, the ToString() method uses the HttpServerUtility.UrlEncode() method to escape the strings before joining them.

C H A P T E R 2 5 ■ C RY P TO G R A P H Y |
863 |
Here’s the first portion of the ToString() method, which escapes and joins the collection settings into one string:
public override string ToString()
{
StringBuilder Content = new StringBuilder();
//Go through the contents and build a
//typical query string
foreach (string key in base.Keys)
{
Content.Append(HttpUtility.UrlEncode(key));
Content.Append("=");
Content.Append(HttpUtility.UrlEncode(base[key]));
Content.Append("&");
}
// Remove the last '&' Content.Remove(Content.Length-1, 1);
...
The next step is to use the ProtectedData class to encrypt the data. This class uses the DPAPI to encrypt the information and its Protect method to return a byte array, so you need to take additional steps to convert the byte array to a string form that’s suitable for the query string. One approach that seems reasonable is the static Convert.ToBase64String() method, which creates a Base64-encoded string. Unfortunately, Base64 strings can include symbols that aren’t allowed in the query string (namely, the equal sign). Although you could create a Base64 string and then URLencode it, this further complicates the decoding stage. The problem is that the ToBase64String() method may also introduce a series of characters that look like URL-encoded character sequences. These character sequences will then be incorrectly replaced when you decode the string.
A simpler approach is to use a different form of encoding. This example uses hex encoding, which replaces each character with an alphanumeric code. The methods for hex encoding aren’t shown in this example, but they are available with the downloadable code.
...
//Now encrypt the contents using DPAPI byte[] EncryptedData = ProtectedData.Protect(
Encoding.UTF8.GetBytes(Content.ToString()), null, DataProtectionScope.LocalMachine);
//Convert encrypted byte array to a URL-legal string
//This would also be a good place to check that data
//is not larger than typical 4 KB query string
return HexEncoding.GetString(EncryptedData);
}
You can place the string returned from EncryptedQueryString.ToString() directly into a query string using the Response.Redirect() method.
The destination page that receives the query data needs a way to deserialize and decrypt the string. The first step is to create a new EncryptedQueryString object and supply the encrypted data. To make this step easier, it makes sense to add a new constructor to the EncryptedQueryString class that accepts the encrypted string, as follows:

864C H A P T E R 2 5 ■ C RY P TO G R A P H Y
public EncryptedQueryString(string encryptedData)
{
// Decrypt data passed in using DPAPI
byte[] RawData = HexEncoding.GetBytes(encryptedData); byte[] ClearRawData = ProtectedData.Unprotect(
RawData, null, DataProtectionScope.LocalMachine); string StringData = Encoding.UTF8.GetString(ClearRawData);
// Split the data and add the contents int Index;
string[] SplittedData = StringData.Split(new char[] { '&' }); foreach (string SingleData in SplittedData)
{
Index = SingleData.IndexOf('='); base.Add(
HttpUtility.UrlDecode(SingleData.Substring(0, Index)), HttpUtility.UrlDecode(SingleData.Substring(Index + 1))
);
}
}
This constructor first decodes the hexadecimal information from the string passed in and uses the DPAPI to decrypt information stored in the query string. It then splits the information back into its parts and adds the key/value pairs to the base StringCollection.
Now you have the entire infrastructure in place to create a simple test page and transmit information from one page to another in a secure fashion.
Creating a Test Page
To try the EncryptedQueryString class, you need two pages—one that sets the query string and redirects the user and another that retrieves the query string. The first one contains a text box for entering information, as follows:
<form id="form1" runat="server"> <div>
Enter some data here: <asp:TextBox runat="server" ID="MyData" /> <br />
<br />
<asp:Button ID="SendCommand" runat="server" Text="Send Info" OnClick="SendCommand_Click" />
</div>
</form>
When the user clicks the SendCommand button, the page sends the encrypted query string to the receiving page, as follows:
protected void SendCommand_Click(object sender, EventArgs e)
{
EncryptedQueryString QueryString = new EncryptedQueryString();
QueryString.Add("MyData", MyData.Text);
QueryString.Add("MyTime", DateTime.Now.ToLongTimeString());
QueryString.Add("MyDate", DateTime.Now.ToLongDateString());
Response.Redirect("QueryStringRecipient.aspx?data=" + QueryString.ToString());
}


866 C H A P T E R 2 5 ■ C RY P TO G R A P H Y
Summary
In this chapter, you learned how take control of the .NET security with advanced techniques. You saw how to use stream-based encryption to protect stored data and the query string. In the next chapter, you’ll learn how to use powerful techniques to extend the ASP.NET security model.
