3

I have posted few questions about Tokens and Password reset and have managed to finally figure this all out. Thanks everyone!

So before reading that certain characters will not work in a query string, I decided to hash the query string but as you've guessed, the plus signs are stripped out.

How do you secure or hash a query string?

This is a sample from a company email I received and the string looks like this:

AweVZe-LujIAuh8i9HiXMCNDIRXfSZYv14o4KX0KywJAGlLklGC1hSw-bJWCYfia-pkBbessPNKtQQ&t=pr&ifl

In my setup, I am simply using a GUID. But does it matter?

In my scenario the user cannot access the password page, even without a GIUD. That's because the page is set to redirect onload if the query string don't match the session variable?

Are there ways to handle query string to give the result like above?

This question is more about acquiring knowledge.

UPDATE:

Here is the Hash Code:

    public static string QueryStringHash(string input)
    {
        byte[] inputBytes = Encoding.UTF8.GetBytes();
        SHA512Managed sha512 = new SHA512Managed();

        byte[] outputBytes = sha512.ComputeHash(inputBytes);
        return Convert.ToBase64String(outputBytes);
    }

Then I pass the HASH (UserID) to a SESSION before sending it as a query string: On the next page, the Session HASH is not the same as the Query which cause the values not to match and rendered the query string invalid.

Note: I created a Class called Encryption that handles all the Hash and Encryption.

Session["QueryString"] = Encryption.QueryStringHash(UserID);

Response.Redirect("~/public/reset-password.aspx?uprl=" +
  HttpUtility.UrlEncode(Session["QueryString"].ToString())); 

I also tried everything mentioned on this page but no luck:

How do I replace all the spaces with %20 in C#

Thanks for reading.

Community
  • 1
  • 1
Asynchronous
  • 3,917
  • 19
  • 62
  • 96
  • 1
    Mads Kristensen has [blogged](http://madskristensen.net/post/HttpModule-for-query-string-encryption.aspx) something about this, you might want to check it out. – aiapatag Jun 10 '13 at 07:36
  • _That's because the page is set to redirect onload ..._ So, if the user disables JavaScript they can view the restricted page? (You should never rely on client-side scripts for security.) – DaoWen Jun 10 '13 at 07:37
  • Do you know about UrlEncode/UrlDecode ? – Aristos Jun 10 '13 at 07:44
  • 2
    I am not using JavaScript and do not plan on doing so for anything security related. Even if the user access the page they will need to have a user name. Getting to the page requires answering the password question and entering a 12 digit account number or having the link emailed. The password reset does not provide Username. By the way the page still redirects if JS is turned off. – Asynchronous Jun 10 '13 at 07:47
  • Aristos, I have tried several Url Encoding and it still does not work. The Query String is different. See above for updated code: Thanks! – Asynchronous Jun 10 '13 at 17:25
  • `SHA512Managed` implements `IDisposable`, so it's creation and lifetime should be wrapped in a `using` statement. – Jesse C. Slicer Jun 10 '13 at 17:40
  • If the value has to be in session, why send a query string at all? Or am I missing something? – Jay Jun 10 '13 at 19:46
  • I am comparing the Query String with the Session when the page loads. This prevents users from saving the link of accessing the link from any other page or computer. When the page loads, it compares the Query string with the version is the session. The session is set to null after the reset is successful. If I do not HASH the string and use a GUID, all works perfectly. I am always looking for improvement and I am not an expert. Please tell me if there is a better way. The Query String is generated at compile time. – Asynchronous Jun 10 '13 at 20:34

2 Answers2

10

The problem is that base64 encoding uses the '+' and '/' characters, which have special meaning in URLs. If you want to base64 encode query parameters, you have to change those characters. Typically, that's done by replacing the '+' and '/' with '-' and '_' (dash and underscore), respectively, as specified in RFC 4648.

In your code, then, you'd do this:

public static string QueryStringHash(string input)
{
    byte[] inputBytes = Encoding.UTF8.GetBytes();
    SHA512Managed sha512 = new SHA512Managed();

    byte[] outputBytes = sha512.ComputeHash(inputBytes);
    string b64 = Convert.ToBase64String(outputBytes);
    b64 = b64.Replace('+', '-');
    return b64.Replace('/', '_');
}

On the receiving end, of course, you'll need to replace the '-' and '_' with the corresponding '+' and '/' before calling the method to convert from base 64.

They recommend not using the pad character ('='), but if you do, it should be URL encoded. There's no need to communicate the pad character if you always know how long your encoded strings are. You can add the required pad characters on the receiving end. But if you can have variable length strings, then you'll need the pad character.

Any time you see base 64 encoding used in query parameters, this is how it's done. It's all over the place, perhaps most commonly in YouTube video IDs.

Olorunfemi Davis
  • 1,001
  • 10
  • 23
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • 1
    Thank you Jay: These are all resources I can look into and learn from overtime. Hey Jim. I was looking into the Replace idea but did not know what other characters sneaked through. I just ran out of energy. I'll look into this some more but I think a solution is near. Thanks again! – Asynchronous Jun 10 '13 at 21:22
  • why not just url encode the special characters instead of changing them to - and _? – Joe Phillips Aug 28 '19 at 15:35
  • @JoePhillips Multiple reasons. First, it increases the size. Second, you then have to decode them, which is more time consuming than single-character conversions. It's a larger departure from standard base64 encoding than just changing the characters. – Jim Mischel Aug 28 '19 at 16:02
1

I did something before where I had to pass a hash in a query string. As you've experienced Base 64 can be pretty nasty when mixed with URLs so I decided to pass it as a hex string instead. Its a little longer, but much easier to deal with. Here is how I did it:

First a method to transform binary into a hex string.

    private static string GetHexFromData(byte[] bytes)
    {
        var output = new StringBuilder();
        foreach (var b in bytes)
        {
            output.Append(b.ToString("X2"));
        }
        return output.ToString();
    }

Then a reverse to convert a hex string back to binary.

    private static byte[] GetDataFromHex(string hex)
    {
        var bytes = new List<byte>();
        for (int i = 0; i < hex.Length; i += 2)
        {
            bytes.Add((byte)int.Parse(hex.Substring(i, 2), System.Globalization.NumberStyles.HexNumber));
        }
        return bytes.ToArray();
    }

Alternatively if you just need to verify the hashes are the same, just convert both to hex strings and compare the strings (case-insensitive). hope this helps.

Jay
  • 6,224
  • 4
  • 20
  • 23