Search code examples
c#.netserial-number

Generate a short code based on a unique string in C#


I'm just about to launch the beta of a new online service. Beta subscribers will be sent a unique "access code" that allows them to register for the service.

Rather than storing a list of access codes, I thought I would just generate a code based on their email, since this itself is unique.

My initial thought was to combine the email with a unique string and then Base64 encode it. However, I was looking for codes that are a bit shorter, say 5 digits long.


Solution

  • If the access code itself needs to be unique, it will be difficult to ensure against collisions. If you can tolerate a case where two users might, by coincidence, share the same access code, it becomes significantly easier.

    Taking the base-64 encoding of the e-mail address concatenated with a known string, as proposed, could introduce a security vulnerability. If you used the base64 output of the e-mail address concatenated with a known word, the user could just unencode the access code and derive the algorithm used to generate the code.

    One option is to take the SHA-1-HMAC hash (System.Cryptography.HMACSHA1) of the e-mail address with a known secret key. The output of the hash is a 20-byte sequence. You could then truncate the hash deterministically. For instance, in the following, GetCodeForEmail("test@example.org") gives a code of 'PE2WEG' :

    // define characters allowed in passcode.  set length so divisible into 256
    static char[] ValidChars = {'2','3','4','5','6','7','8','9',
                       'A','B','C','D','E','F','G','H',
                       'J','K','L','M','N','P','Q',
                       'R','S','T','U','V','W','X','Y','Z'}; // len=32
    
    const string hashkey = "password"; //key for HMAC function -- change!
    const int codelength = 6; // lenth of passcode
    
    string GetCodeForEmail(string address)
    {
        byte[] hash;
        using (HMACSHA1 sha1 = new HMACSHA1(ASCIIEncoding.ASCII.GetBytes(hashkey)))
            hash = sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(address));
        int startpos = hash[hash.Length -1] % (hash.Length - codelength);
        StringBuilder passbuilder = new StringBuilder();
        for (int i = startpos; i < startpos + codelength; i++)
            passbuilder.Append(ValidChars[hash[i] % ValidChars.Length]);
        return passbuilder.ToString();
    }