0

I'm personally tired of reimplementing "a ticket" mechanism for my web projects for some one-time operations like account activation or password reset. I know it's simple but it requires me to keep (and persist!) two pieces of data: a ticket itself and an expiration date.

So I have this idea, to generate a unique string with embedded datetime (expiration date) in it, which we can check upon receiving as a part of url-request.

I've started with this:

var clearTicket = Convert.ToInt64(expiration.ToString("yyyyMMddhhmm")).ToString("x12") + key.ToString("N");

But I want it to be more compact. Something BaseXX-ish I suppose. Any ideas how to implement this encoding/decoding efficiently (considering url-safe charset)?

Kostassoid
  • 1,870
  • 2
  • 20
  • 29
  • Doesn't look very secure... it would be easy to alter the expiration date – Thomas Levesque Jun 17 '11 at 22:42
  • it would indeed, but it won't affect security in any way. this string will be stored in database, in fact i will search user profile by it, and only after that i will extract the datetime to see if the token has expired. – Kostassoid Jun 18 '11 at 06:06

3 Answers3

2

It took me some time, so I hope it helps:

First of all, you should substract 200000000000 from "yyyyMMddhhmm", because you actually don't need the first two digits for the next 88 years.

Here is the implementation for encoding and decoding Base64, using only URL-safe characters.
If you have any questions, feel free to ask.

public string Base64Encode (Int64 Number)
{
    string HelpString = "";
    if (Number >= 64)
    {
        HelpString = Base64Encode(Number / 64);
    }
    return (HelpString += Base64EncodeHelper(Number % 64));
}

public string Base64EncodeHelper(Int64 Number)
{
    string HelpString = "";
    Number += 65;
    if ((Number >= 65 && Number <= 90) || (Number >= 97 && Number <= 122))  // 0 - 25 and 32 - 57
    {
        HelpString = Convert.ToString((char)Number);
    }
    else if (Number >= 91 && Number <= 96)                                  // 26 - 31
    {
        HelpString = Convert.ToString((char)(Number - 43));
    }
    else if (Number >= 123 && Number <= 126)                                // 58 - 61
    {
        HelpString = Convert.ToString((char)(Number - 69));
    }
    else if (Number == 127)                                                 // 62
    {
        HelpString = "-";
    }
    else                                                                    // 63
    {
        HelpString = "_";
    }
    return (HelpString);
}

public Int64 Base64Decode(string Encoded)
{
    Int64 Result = 0, HelpInt = 0;
    int i = Encoded.Length - 1;
    foreach (char Character in Encoded)
    {
        int CharInInt = (int)Character;
        if (Character == '_')
        {
            HelpInt = 63;
        }
        else if (Character == '-')
        {
            HelpInt = 62;
        }
        else if (((CharInInt + 69) >= 123) && ((CharInInt + 69) <= 126))
        {
            HelpInt = CharInInt + 4;
        }
            else if (((CharInInt + 43) >= 91) && ((CharInInt + 43) <= 96))
        {
            HelpInt = CharInInt - 22;
        }
        else
        {
             HelpInt = CharInInt - 65;
        }
        Result += Convert.ToInt64((Math.Pow(64, Convert.ToDouble(i))) * HelpInt);
        i--;
    }
    return Result;
}
Zeus
  • 123
  • 1
  • 6
  • thanks, interesting method! you're right about first digits of year, don't need them. and i can't check now but since you've implemented your own base64, i believe it's url-safe? i've managed to create a working implementation of the idea using whole hex string of date and guid (224 bits or so) but couldn't post here because of rules. i will do so on monday. but i'm not entirely happy with my base64 conversion so i'll try your method. – Kostassoid Jun 18 '11 at 06:30
  • well, the encoded String consists of small letters [a,z], capital letters [A,Z], numbers [0,9], "dash" [-] and underscore [_]. All of them are allowed in URLs. – Zeus Jun 18 '11 at 17:08
0

You can extend string like this:

public static string ToURLParameter(this string text)
{
    if (String.IsNullOrEmpty(text)) return "";

    // to lowercase, trim extra spaces
    text = text.Trim();

    var len = text.Length;
    var sb = new StringBuilder(len);
    bool prevdash = false;
    char c;

    //
    text = text.Replace('Å', 'A');
    text = text.Replace('å', 'a');
    text = text.Replace('Ä', 'A');
    text = text.Replace('ä', 'a');
    text = text.Replace('Ö', 'O');
    text = text.Replace('ö', 'o');

    for (int i = 0; i < text.Length; i++)
    {
        c = text[i];
        if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-')
        {
            if (!prevdash)
            {
                sb.Append('-');
                prevdash = true;
            }
        }
        else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'))
        {
            sb.Append(c);
            prevdash = false;
        }
        if (i == 80) break;
    }

    text = sb.ToString();
    // remove trailing dash, if there is one
    if (text.EndsWith("-"))
        text = text.Substring(0, text.Length - 1);

    return text;
}

and then you can use it on any variable that is string like:

string something = "asdfljasdklf";

<%= something.ToUrlParameter() %>
Lasse Edsvik
  • 9,070
  • 16
  • 73
  • 109
  • If you use asp.net mvc you can use url routing with constraints in for the values in the url. – Lasse Edsvik Jun 17 '11 at 08:51
  • Lasse Edsvik - but I have to decode the string when I receive it as a parameter later. Besides, i can't say for sure if it'll stay unique after ToUrlParameter() transformation. – Kostassoid Jun 17 '11 at 08:56
0

I've finally created a solution using techniques described in these answers:

Compressing big number (or string) to small value

How do you convert Byte Array to Hexadecimal String, and vice versa?

Works great!

Thanks you all for your help!

Community
  • 1
  • 1
Kostassoid
  • 1,870
  • 2
  • 20
  • 29