Implementing the Caesar Cipher in Ruby


Caesar cipher is the simplest and most widely known encryption technique. It is also known as Caesar’s cipher, shift cipher, Caesar’s code, Caesar shift, or ROT N (ROT13 is the most famous one, shifting letters by 13).

It is very simple because it just works for letters between A and Z, ignoring all special characters, such as dots, whitespaces, question marks, and special letters, like Ç or Á.

Starting our implementation, we need to create a class that will know what we want to cipher and how many rotations we will do.

class Caeser
attr_reader :text, :rotation
def initialize(text, rotation)
@text = text
@rotation = rotation
end
end
view raw caeser_class.rb hosted with ❤ by GitHub

Having done that, we need to shift how many chars we want, so we need to add the shift method:

def shift(byte, initial_byte, limit_byte)
new_byte = byte + rotation
return initial_byte - (limit_byte - new_byte) - 1 if new_byte > limit_byte
new_byte
end
view raw caeser_shift.rb hosted with ❤ by GitHub

As you can see, we have a small problem at this point. If the new_byte (the old byte plus how many bytes do you want to rotate), is bigger than Z or z, we must start shifting it from A or a again. When this happens, we decrease one, because we have to take the initial char into consideration.

In this example, initial_byte is the byte for A or a and limit_byte is the byte for Z or z. In this way, we always rotate just between the letters.

So, we have to discover how we can find the bytes for each char inside of a string. Diving into Ruby, this is easily done by calling the method bytes. Knowing this, we have the following:

  • A is represented by the byte 65.
  • Z is represented by the byte 90.
  • a is represented by the byte 97.
  • z is represented by the byte 122.
'AZaz'.bytes
view raw bytes.rb hosted with ❤ by GitHub

The next step is to find our initial_byte and limit_byte or if we should ignore that char. In order to do that, we will check if the bytes present inside of a given text are between the range bytes for the letters that we want, if not, we will just return the byte.

def cipher
text.bytes.map do |byte|
case byte
when 65..90 then shift(byte, 65, 90)
when 97..122 then shift(byte, 97, 122)
else byte
end
end.pack('c*')
end
view raw cipher.rb hosted with ❤ by GitHub

The fastest way to convert these bytes into a string again is by calling pack. In short, pack converts the given array into a string. We also give c as a parameter to the method. In this case, c means an 8-bit signed integer, and * means the whole array should be converted. Now we are able to cipher a message using the Caesar technique, if we want to decipher it, we should shift it in the opposite direction, like here:

def cipher
text.bytes.map do |byte|
case byte
when 65..90 then shift(byte, 65, 90)
when 97..122 then shift(byte, 97, 122)
else byte
end
end.pack('c*')
end
view raw cipher.rb hosted with ❤ by GitHub

It takes the same idea of the shift method, but does it in the “other” direction.

The decipher’s method should look exactly the same as the cipher. Just duplicate cipher and change the method signature to decipher.

So, let’s try our code!

def unshift(byte, initial_byte, limit_byte)
new_byte = byte - rotation
return limit_byte - (initial_byte - new_byte) + 1 if new_byte < initial_byte
new_byte
end

You also can decipher the text, like the following:

Caeser.new('Jul qvq gur puvpxra pebff gur ebnq?', 13).decipher
view raw caeser_run_2.rb hosted with ❤ by GitHub

An interesting thing happens when you use rotation equal to 13. Between A and Z, we have exactly 26 letters, so does not matter if you call cipher or decipher, it will return the same result.

caeser = Caeser.new('Why did the chicken cross the road?', 13)
caeser.cipher == caeser.decipher
view raw caeser_run_3.rb hosted with ❤ by GitHub

To have different results, you need to change the rotation to another value!

Unfortunately, this solution does not work for strings with multi-byte characters. Thanks for some guys on Slack that notice that! If you want a solution that works with multi-byte chars, you should implement a solution based on mod division.

This is the first post about cipher, my idea is to implement some techniques and try to easily explain it here. If you want to take a look at the whole code, please check at Github .

You also can find me on Twitter , Github , or LinkedIn .

See also