Search code examples
rubyencryptionencryption-symmetric

Simple Encryption in Ruby without external gems


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


Solution

  • 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:

    enter image description here

    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.)