Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 767
Ben Foster Avatar asked Dec 28 '22 16:12

Ben Foster


1 Answers

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("[email protected]") 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();
}
like image 153
drf Avatar answered Dec 30 '22 10:12

drf