noctufaber
noctufaber

Reputation: 794

How to generate hard to guess referral / coupon codes?

I'm having a difficult time coming up with an algorithm that can create short (8 character) referral codes. I want to use an easy to remember pattern where it's not possible to have offensive words show up in the codes. I also don't want any letters getting confused with numbers -- so no 1's, l's, 0's and O's. The pattern I've come up with is aa22aa22. This pattern is basically two alphabetical characters followed by two numerical characters followed by two alphabetical characters followed by two numerical characters. The alphabetical characters are all lower case. This pattern supports over 4 billion possible codes.

Now for the tricky part. I need to store the generated codes in Salesforce. I think this needs to be done in a non-random way because if I do it randomly, I have to check for collisions against already generated codes. This then gets into the governor limits that Salesforce imposes on you. If you're not familiar with governor limits it basically means if you query the database too many times or if your process runs too long the underlying system throws a governor limit error. Random code creation introduces uncertainty regarding how many queries it will take to find a code that doesn't collide with a previously created code. So, now it basically comes down to creating codes that are guaranteed to never repeat and that means creating them sequentially. The problem with a sequential method is the codes are easy to guess.

Yes, I could have a non-Salesforce datastore that could act as the source of record and go with a random method and do the collision check there, but I'd like to see if the worldwide community has any ideas that might work. I have tried to find a weak symmetrical encryption algorithm that can yield 8 character ciphers, but I've had no luck so far.

Upvotes: 5

Views: 976

Answers (2)

vlad_tepesch
vlad_tepesch

Reputation: 6915

I would chose an approach like using some random number generator to create a non -repeatable sequence like the one suggested here: https://stackoverflow.com/a/196164/2331592

You have to chose a generator that supports up to [number of possible coupons] combinations. Each time you draw a random number then you got another number that is the index of a possible coupon (--> permutation) that can be directly translated into a coupon (but since your coupons digits have different bases it is not so easy as changing numbers base)

example:

if your code pattern looks like a1a there a stands for [abcde] and 1 for [123]. this would make 75 permutations.

00 a1a    15 b1a     30 c1a     45 d1a     60 e1a
01 a1b    16 b1b     31 c1b     46 d1b     61 e1b
02 a1c    17 b1c     32 c1c     47 d1c     62 e1c
03 a1d    18 b1d     33 c1d     48 d1d     63 e1d
04 a1e    19 b1e     34 c1e     49 d1e     64 e1e
05 a2a    20 b2a     35 c2a     50 d2a     65 e2a
06 a2b    21 b2b     36 c2b     51 d2b     66 e2b
07 a2c    22 b2c     37 c2c     52 d2c     67 e2c
08 a2d    23 b2d     38 c2d     53 d2d     68 e2d
09 a2e    24 b2e     39 c2e     54 d2e     69 e2e
10 a3a    25 b3a     40 c3a     55 d3a     70 e3a
11 a3b    26 b3b     41 c3b     56 d3b     71 e3b
12 a3c    27 b3c     42 c3c     57 d3c     72 e3c
13 a3d    28 b3d     43 c3d     58 d3d     73 e3d
14 a3e    29 b3e     44 c3e     59 d3e     74 e3e

chosing a simple LCG based on
x = (a*x + c) mod m
setting
x = 1 (any number can be used)
a = 5 (any large number can be used - just make sure that your arithmetic does not overflow)
c = 0
m = 73

Setting m to be the next smallest prime to your permutation count to make sure that the generator always creates a valid number although that excludes a number of valid combinations at the end - so this generator will never generates the #73 and #74 and also never the #0 since then always 0 is created

this is the generator output until it loops again:

01:  5      11: 31      21: 17      31: 47     41: 14    51: 43    61: 33    71: 44
02: 25      12:  9      22: 12      32: 16     42: 70    52: 69    62: 19    72:  1
03: 52      13: 45      23: 60      33: 7      43: 58    53: 53    63: 22    --> 01:  5
04: 41      14:  6      24:  8      34: 35     44: 71    54: 46    64: 37
05: 59      15: 30      25: 40      35: 29     45: 63    55: 11    65: 39
06:  3      16:  4      26: 54      36: 72     46: 23    56: 55    66: 49
07: 15      17: 20      27: 51      37: 68     47: 42    57: 56    67: 26
08:  2      18: 27      28: 36      38: 48     48: 64    58: 61    68: 57
09: 10      19: 62      29: 34      39: 21     49: 28    59: 13    69: 66
10: 50      20: 18      30: 24      40: 32     50: 67    60: 65    70: 38

Each time we get a different index. from that index we can look up in the above table the code, but we also can calculate it:

The digits in our code have different values (as in out decimal digit system, where each digit has a value ten times higher than the previous digit)

a positions have 5 possibilities and 1 has 3 possibilies.

             a3,     a2,    a1
possibilies:  5       3     5
digit value: a3*3*5  a2*5   a1*1
digit value: a3*15   a2*5   a1*1

each of the possibilities has an attached value:

letters  a b c d e
value    0 1 2 3 4

numbers  1 2 3
value    0 1 2  <-- this is a bit counter intuitive - but we need to start at 0

e3b -->      e≙4     3≙2   b≙1
             4*3*5 + 2*5   + 1*1
          =  60    + 10    + 1
          =  71
code e3b --> #71

the other way around to get the code from an index without requiring a look up table.
Each digit can be calvulated by dividing (integer devision) the value by the digits value and take the modulo of the number of possibilites for this digit

#51
    a1 = (51 /  1) mod 5 = 1 ≙ b    
    a2 = (51 /  5) mod 3 = 1 ≙ 2
    a3 = (51 / 15) mod 5 = 3 ≙ d

#51 --> code d2b

As a general hint: i would decouple the representation of the codes from the internal number. The number in the example would be the permutation-index. You only need to know the last issued index, to generate the next one.

How the Index is represented is may be independent from this. in our case the a1a representation.

Some things to look up:
https://en.wikipedia.org/wiki/Positional_notation for number representations
https://en.wikipedia.org/wiki/Linear_congruential_generator to learn how to generator works and how to parameterize it

Upvotes: 2

Alexander Oh
Alexander Oh

Reputation: 25641

Before doing something complex and look into randomness, why not just generate a UUID?

https://en.wikipedia.org/wiki/Universally_unique_identifier

Upvotes: 0

Related Questions