Wednesday, November 08, 2006

Encrypting/Decrypting QueryStrings In .NET

I was messing around with a secure way to pass data via a querystring today. In business you sometimes don't have a choice. What I came up with was a secure two-way encryption/decryption class. I can now encode/decode any piece of data without having knowledge of the key.


The Methods
I have an error page in an ASP.NET application. The page is basically going to act as a catch all page for errors in my forum application. I want to be able to send it exception strings without having to rely upon Server.GetLastError. I researched some of the System.Security.Cryptography namespace for a seed-based algorithm. DES seemed to fit that bill perfectly. MSDN even went so far as to write the Encrypt and Decrypt methods for me.



1 // Encrypt the string.
2 public byte[] Encrypt(string PlainText, SymmetricAlgorithm key)
3 {
4 // Create a memory stream.
5 MemoryStream ms = new MemoryStream();
6
7 // Create a CryptoStream using the memory stream and the
8 // CSP DES key.
9 CryptoStream encStream = new CryptoStream(ms, key.CreateEncryptor()
10 CryptoStreamMode.Write);
11
12 // Create a StreamWriter to write a string
13 // to the stream.
14 StreamWriter sw = new StreamWriter(encStream);
15
16 // Write the plaintext to the stream.
17 sw.WriteLine(PlainText);
18
19 // Close the StreamWriter and CryptoStream.
20 sw.Close();
21 encStream.Close();
22
23 // Get an array of bytes that represents
24 // the memory stream.
25 byte[] buffer = ms.ToArray();
26
27 // Close the memory stream.
28 ms.Close();
29
30 // Return the encrypted byte array.
31 return buffer;
32 }
33
34 // Decrypt the byte array.
35 public string Decrypt(byte[] CypherText, SymmetricAlgorithm key)
36 {
37 // Create a memory stream to the passed buffer.
38 MemoryStream ms = new MemoryStream(CypherText);
39
40 // Create a CryptoStream using the memory stream and the
41 // CSP DES key.
42 CryptoStream encStream = new CryptoStream(ms, key.CreateDecryptor(),
43 CryptoStreamMode.Read);
44
45 // Create a StreamReader for reading the stream.
46 StreamReader sr = new StreamReader(encStream);
47
48 // Read the stream as a string.
49 string val = sr.ReadLine();
50
51 // Close the streams.
52 sr.Close();
53 encStream.Close();
54 ms.Close();
55
56 return val;
57 }


This is a great start if I wanted byte arrays. I need to flatten this array out so I can use it like a string. I decided to write a couple more methods named EncryptToString and DecryptToString.



1 public string EncryptToString(string PlainText, SymmetricAlgorithm key)
2 {
3 byte[] buffer = this.Encrypt(PlainText, key);
4 string temp = string.Empty;
5
6 for (int i = 0; i < buffer.Length; i++)
7 {
8 temp += buffer[i].ToString("x2");
9 }
10
11 return temp;
12 }
13
14 public string DecryptFromString(string CypherText, SymmetricAlgorithm key)
15 {
16 int arrayLen = (CypherText.Length / 2);
17 byte[] buffer = new byte[arrayLen];
18
19 for (int i = 0; i < (CypherText.Length / 2); i++)
20 {
21 buffer[i] = Convert.ToByte(Convert.ToInt32(CypherText.Substring(i * 2, 2), 16));
22 }
23
24 return this.Decrypt(buffer, key);
25 }


These methods simply use the two that MSDN provided, but add some app-centric features. EncryptToString will get the byte array from the Encrypt method. Then it will create, and append to, a string while converting each item in the byte array to a two-digit hexadecimal number. The reason I converted to hex was to shorten the total length of the string. A handy byproduct was that it added yet another layer of obfuscation to my data making that much more difficult to decrypt. DecryptFromString takes the encrypted string and essentially reverses the process.


What about the key?
This is my favorite part of the entire solution. The key is generated as an application level variable in the Global.asax. I have to admit that I don't use the Global.asax that much, but it seemed like a reasonable place for the code to generate the key.



1 void Application_Start(object sender, EventArgs e)
2 {
3 DESCryptoServiceProvider key = new DESCryptoServiceProvider();
4 Application.Add("SecureKey", key);
5 }


Of course, this could have been Session level, but I didn't need to change the key that frequently. When adding this code to your page be sure to import the namespace, <%@ Import Namespace="System.Security.Cryptography" %>.


In Action
The final piece is how we would use this in the code. Another reason I went this route was to simplify what I needed to code when using this class. In this example, we have made reference to the class containing the encryption/decryption methods. I called mine, Crypto. We'll be encoding an Exception string and sending it to an error page.



1 Crypto cryptKeeper = new Crypto();
2 string encryptedText = cryptKeeper.EncryptToString(
3 new Exception("The request could not be completed.").ToString(),
4 Application["SecureKey"] as System.Security.Cryptography.SymmetricAlgorithm);
5
6 Response.Redirect("~\\Error.aspx?msg=" + encryptedText);


The encryptedText variable looks something like this, 6998d04ec523b093fe0ce9ce9994915a076199ef1f1663d3273e23
72cb5b53dea83f931016f58dc9ae21a4f335de16038eecaee374cacc28. The error page takes the parameter and decrypts it.


1 Crypto cryptKeeper = new Crypto();
2 if (Request.Params["msg"] != null)
3 {
4 lblMsg.Text = cryptKeeper.DecryptFromString(Request.Params["msg"],
5 Application["SecureKey"] as SymmetricAlgorithm);
6 }

Notes
Because I made my key an Application variable, it will be the same for every user of the site and only change when the site is restarted. If I were to make it a Session variable, it would change for every user, and that may be the way to go for me in the future.


I think the possibilities with this are endless. You don't have to use it to pass secure data via a querystring. It can be used to securely store data in your database. You can code your app to generate and store the DES key daily, weekly, or monthly. Access levels to data should be setup to only allow authorized personnel access to the key table. It's not so much different than what my company spent $50,000 on.

1 comment: