I need a simple encryption for some text strings. I want to create coupon codes and make them look cool so subsequently created code should look very different. (And besides looking cool, it shouldn't be easy to guess a code.) But I want to be able to decrypt them again. So the algorithm must be reversible.
I alread tried some stuff with moving bits around so they look kind of random already. But two subsequent codes (just one bit different) of course look very similar.
Any suggestions? I would like to do that without using external gems.
Philip
The solution is kind of from scratch but based on this: https://math.stackexchange.com/questions/9508/looking-for-a-bijective-discrete-function-that-behaves-as-chaotically-as-possib
The simplest way presented is using a * x + b (mod 2^n)
Obviously this is no real encryption and really only useful if you want to create sequential coupon codes without using much code.
So to implement this, you first need to pick a, b and n. (a must be odd) For example a=17
, b=37
and n=27
. Also we need to find "a^(-1)
" on "mod 2^n". It's possible to do this on https://www.wolframalpha.com using the ExtendedGcd function:
So the inverse of a
is therefore 15790321
. Putting all this together:
A=17
B=37
A_INV=15790321
def encrypt(x)
(A*x+B)%(2**27)
end
def decrypt(y)
((y-B)*A_INV)%(2**27)
end
And now you can do:
irb(main):038:0> encrypt(4)
=> 105
irb(main):039:0> decrypt(105)
=> 4
Obviously we want the coupon codes to look cool. So 2 extra things are needed: start the sequence at 4000 or so, so the codes are longer. Also convert them into something alpha-numeric, that's also an easy one with Ruby:
irb(main):050:0> decrypt("1ghx".to_i(36))
=> 4000
irb(main):051:0> encrypt(4000).to_s(36)
=> "1ghx"
One nice additional property is that consecutive numbers are different enough that guessing is practically impossible. Of course we assume that the users are not crypto analysts and if someone indeed guesses a valid number, it's not the end of the world: :-)
irb(main):053:0> encrypt(4001).to_s(36)
=> "1gie"
irb(main):054:0> decrypt("1gie".to_i(36))
=> 4001
Let's try to naively "hack" it by counting from 1gie
to 1gif
:
irb(main):059:0* decrypt("1gif".to_i(36))
=> 15794322
That's completely out of range, there are just 2000 or so coupons anyways - not a million. :-) Also if I remember correctly one can experiment a bit with the parameters, so subsequent numbers look more chaotic.
(Pick a larger n
for longer codes and vice-versa. Base 36
means 6
bits are needed for each character ("Math.log(36, 2)
"). So n=27
allows for up to 5 characters.)