I came across this question on stackoverflow today and realized that I solved this very issue a while back, but hadn't yet blogged about it. The problem is simple: you have some sensitive data in the form of a query string and want it encrypted so the end user can't see it. But you need the ability to decrypt this value in your application to do something with it.
I found a great set of base classes to solve this but for the most part it comes down to one class. This class requires a 16 char key of some kind to do the encryption and a value to encrypt. You can also set an expiration value if needed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System.Collections.Specialized;
using System.Security;
using System.Text;
using System.Web;
using EncryptionMVC.Security.Encryption.Utility.Interfaces;
using EncryptionMVC.Security.Encryption.Utility;
namespace Security.Encryption.QueryString
{
/// <summary>
/// Provides a secure means for transfering data within a query string.
/// </summary>
public class SecureQueryString : NameValueCollection
{
private string timeStampKey = '__TS__';
private string dateFormat = 'G';
private IEncryptionUtility mEncryptionUtil;
private DateTime m_expireTime = DateTime.MaxValue;
/// <summary>
/// Creates an instance with a specified key.
/// </summary>
/// <param name='key'>The key used for cryptographic functions, required 16 chars in length.</param>
public SecureQueryString(string key) : base()
{
mEncryptionUtil = new EncryptionUtility(key);
}
/// <summary>
/// Creates an instance with a specified key and an encrypted query string.
/// </summary>
/// <param name='key'>The key used for cryptographic functions, required 16 chars in length.</param>
/// <param name='queryString'>An encrypted query string generated by a <see cref='SecureQueryString'/> instance.</param>
public SecureQueryString(string key, string queryString) : this(key)
{
Deserialize(DecryptAndVerify(queryString));
CheckExpiration();
}
/// <summary>
/// Returns a encrypted query string.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return EncryptAndSign(Serialize());
}
private void Deserialize(string queryString)
{
string[] nameValuePairs = queryString.Split('&');
for (int i = 0; i <= nameValuePairs.Length - 1; i++) {
string[] nameValue = nameValuePairs(i).Split('=');
if (nameValue.Length == 2) {
base.Add(nameValue(0), nameValue(1));
}
}
if (base.GetValues(timeStampKey) != null) {
string[] strExpireTime = base.GetValues(timeStampKey);
m_expireTime = Convert.ToDateTime(strExpireTime(0));
}
}
private string Serialize()
{
StringBuilder sb = new StringBuilder();
foreach (string key in base.AllKeys) {
sb.Append(key);
sb.Append('=');
sb.Append(base.GetValues(key)(0).ToString());
sb.Append('&');
}
sb.Append(timeStampKey);
sb.Append('=');
sb.Append(m_expireTime.ToString(dateFormat));
return sb.ToString();
}
private string DecryptAndVerify(string input)
{
return mEncryptionUtil.Decrypt(input);
}
private string EncryptAndSign(string input)
{
return mEncryptionUtil.Encrypt(input);
}
private void CheckExpiration()
{
if (DateTime.Compare(m_expireTime, DateTime.Now) < 0) {
throw new ExpiredQueryStringException();
}
}
/// <summary>
/// Gets or sets the timestamp in which this string should expire
/// </summary>
public DateTime ExpireTime {
get { return m_expireTime; }
set { m_expireTime = value; }
}
}
}
To encrypt some value and pass it to another action in MVC you would do something like the below.
1
2
3
4
5
6
7
8
9
10
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection collection)
{
SecureQueryString qs = new SecureQueryString(mKey);
qs('YourName') = collection('name');
qs.ExpireTime = DateTime.Now.AddMinutes(2);
Response.Redirect('Home.aspx/About?data=' + HttpUtility.UrlEncode(qs.ToString()));
}
In the action that we redirect to, you would need to have this same key and the query string value itself to decrypt it. Keep in mind that if you don't have the correct key or if you try to decrypt the value after the expiration, the class will throw an exception.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ActionResult About()
{
if (Request('data') != null) {
try {
SecureQueryString qs = new SecureQueryString(mKey, Request('data'));
ViewData('Message') = 'Your name is ' + qs('YourName');
}
catch (Exception ex) {
}
}
return View();
}
I didn't spend much time explaining the source in depth because it has been so long since I wrote it. Also keep in mind this was long before my test first days ... (but it does appear to work)
As always, the source code for this sample is available for download.