That was the essence of my suggestion - put the connection string on the QueryString.
Shortly thereafter Steve Orr, whose work I respect, responded that putting a connection
string on the querystring posed a substantial security risk, which is correct.
So what I'm about to offer here is a nice, compact way to encapsulate the QueryString
in ASP.NET as an object that makes it much easier to manipulate, along with some
nice code that will perform ASCII-to-HEX scrambling on both the names -- and
the values -- of everything on the querystring. Actually you probably could never
get an unencrypted connection string on the querystring anyway, it contains illegal
"=" characters, so you'd have no choice but to mangle it in some way.
(Similarly, we cannot use Base64 encoding since a Base64 encoded string ends
with one or two equals signs.)
This is not "encryption" - it's only scrambling or "obfuscation" - which is typically all you need
to keep your "stuff" away from prying eyes. If you truly need strong
encryption, my suggestion is -- don't use the querystring at all; find another
way. Of course, you can add to this, for example doing TRIPLE-DES encryption
before performing the ASCI-To-HEX, and the reverse for decrypting, if you wanted
to do "overkill". But the bottom line is, it's best not to put sensitive
data on the querystring at all -- there are other ways to pass the data.
First, I make use of Bobby DeRosa's QueryString class which is shown here virtually
unchanged from his original post:
using System.Collections.Specialized ;
using System.Collections;
using System.Web;
using System;
namespace PAB.Utils
{
// from Bobby DeRosa :
// http://www.csharper.net/blog/
// querystring_class_useful_for_querystring_manipulation__appendage__etc.aspx
public class QueryString : NameValueCollection
{
private string document;
public string Document
{
get
{
return document;
}
}
public QueryString()
{
}
public QueryString(NameValueCollection clone) : base(clone)
{
}
public static QueryString FromCurrent()
{
return FromUrl(HttpContext.Current.Request.Url.AbsoluteUri);
}
public static QueryString FromUrl(string url)
{
string[] parts = url.Split("?".ToCharArray());
QueryString qs = new QueryString();
qs.document = parts[0];
if(parts.Length == 1)
return qs;
string[] keys = parts[1].Split("&".ToCharArray());
foreach(string key in keys)
{
string[] part = key.Split("=".ToCharArray());
if(part.Length == 1)
qs.Add(part[0], "");
qs.Add(part[0], part[1]);
}
return qs;
}
public void ClearAllExcept(string except)
{
ClearAllExcept(new string[] { except });
}
public void ClearAllExcept(string[] except)
{
ArrayList toRemove = new ArrayList();
foreach(string s in this.AllKeys)
{
foreach(string e in except)
{
if(s.ToLower() == e.ToLower())
if(!toRemove.Contains(s))
toRemove.Add(s);
}
}
foreach(string s in toRemove)
this.Remove(s);
}
public override void Add(string name, string value)
{
if(this[name] != null)
this[name] = value;
else
base.Add(name, value);
}
public override string ToString()
{
return ToString(false);
}
public string ToString(bool includeUrl)
{
string[] parts = new string[this.Count];
string[] keys = this.AllKeys;
for(int i = 0; i < keys.Length; i++)
parts[i] = keys[i] + "=" + HttpContext.Current.Server.UrlEncode(this[keys[i]]);
string url = String.Join("&", parts);
if((url!=null || url!=String.Empty) && !url.StartsWith("?"))
url = "?" + url;
if(includeUrl)
url = this.document + url;
return url;
}
}
}
What the above gives us is an easy way to work with the Querystring as a true "object"
rather than having to write what I like to refer to as "BS code" just
to get items and so on. For example, with a real object encapsulating the Querystring,
we can write code like this:
private void btnGo_Click(object sender, System.EventArgs e)
{
QueryString qs = new QueryString();
qs.Add(this.txtName1.Text,this.txtValue1.Text);
qs.Add(this.txtName2.Text,this.txtValue2.Text);
qs.Add(this.txtName3.Text,this.txtValue3.Text);
string strRedirect= "WebForm2.aspx";
QueryString qsEncrypted = PAB.Utils.Encryption.EncryptQueryString(qs);
strRedirect+=qsEncrypted.ToString();
Response.Redirect(strRedirect,true);
}
Tell me that isn't a lot easier to work with! You can see the call the encryption
utility passes an instance of the entire Querystring object, and receives back
a new one, encrypted - both names and values. All we need to do is call the ToString()
method and add it to the base redirect URL. Now, on the receiving end:
private void Page_Load(object sender, System.EventArgs e)
{
QueryString qs = QueryString.FromCurrent();
txtRaw.Text+=((QueryString)PAB.Utils.Encryption.DecryptQueryString(qs)).ToString();
}
Finally, here's the Encryption Utility class that works with this, with the Hex routines
I finally settled on:
using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Security.Cryptography;
using System.Globalization ;
using System.Web;
namespace PAB.Utils
{
public class Encryption
{
private Encryption()
{
}
public static QueryString EncryptQueryString(QueryString queryString)
{
QueryString newQueryString = new QueryString();
string nm=String.Empty;
string val=String.Empty;
foreach(string name in queryString)
{
nm= name;
val=queryString[name];
newQueryString.Add(PAB.Utils.Encryption.Hex(nm), PAB.Utils.Encryption.Hex(val));
}
return newQueryString;
}
public static QueryString DecryptQueryString(QueryString queryString)
{
QueryString newQueryString = new QueryString();
string nm;
string val;
foreach(string name in queryString)
{
nm=PAB.Utils.Encryption.DeHex(name);
val=PAB.Utils.Encryption.DeHex(queryString[name]);
newQueryString.Add(nm, val );
}
return newQueryString;
}
public static string DeHex(string hexstring)
{
string ret = String.Empty;
StringBuilder sb = new StringBuilder(hexstring.Length /2);
for(int i=0;i<= hexstring.Length-1;i=i+2)
{
sb.Append((char)int.Parse(hexstring.Substring(i,2), NumberStyles.HexNumber));
}
return sb.ToString();
}
public static string Hex(string sData)
{
string temp = String.Empty;;
string newdata=String.Empty;
StringBuilder sb = new StringBuilder(sData.Length *2);
for (int i = 0;i < sData.Length;i++)
{
if ((sData.Length - (i + 1)) > 0)
{
temp = sData.Substring(i,2);
if (temp == @"\n") newdata += "0A";
else if (temp == @"\b") newdata += "20";
else if (temp == @"\r") newdata += "0D";
else if (temp == @"\c") newdata += "2C";
else if (temp == @"\\") newdata += "5C";
else if (temp == @"\0") newdata += "00";
else if (temp == @"\t") newdata += "07";
else
{
sb.Append( String.Format("{0:X2}", (int)(sData.ToCharArray())[i]));
i--;
}
}
else
{
sb.Append( String.Format("{0:X2}", (int)(sData.ToCharArray())[i]));
}
i++;
}
return sb.ToString();
}
}
}
I experimented with XOR, ROT13, ROT39 and ASCII-to-Hex, which is the method I finally
settled on. Since this method doesn't require any UrlEncoding normalization,
that part of the QueryString class has been commented out. The net result of
all this is that this QueryString (which would not even pass as it contains illegal
characters):
WebForm2.aspx?connstring=server=xyz;database=yoda;uid=jocko;pwd=youbetcha;
would now look like so:
WebForm2.aspx?636F6E6E737472696E67=7365727665723D78797A3B646174616261
73653D796F64613B7569643D6A6F636B6F3B7077643D796F756265746368613B
You can see the two static methods EncryptQuerystring and DecryptQuerystring which
accept and return Querystring objects. So now, if you have a need to scramble
your QueryStrings for "basic security", this becomes an easy way to
handle it. A nice side benefit of this is that now you can put whatever you want
into a querystring item - spaces, semicolons, slashes, equals signs -- you name
it, and it will be faithfully reproduced on the receiving end.
N.B. Thanks to commenters Steve and Richard on the original of this article for their
coding suggestions, both of which have been incorporated into the downloadable
source code below. And - If you truly want secure transmission of items from
one page to another, DO NOT put them on the querystring!
Download the Visual Studio 2010 Solution.