Search code examples
algorithmmatlabencryptionmodulusalgebra

Caesar's cypher encryption algorithm


Caesar's cypher is the simplest encryption algorithm. It adds a fixed value to the ASCII (unicode) value of each character of a text. In other words, it shifts the characters. Decrypting a text is simply shifting it back by the same amount, that is, it substract the same value from the characters.

My task is to write a function that:

  • accepts two arguments: the first is the character vector to be encrypted, and the second is the shift amount.
  • returns one output, which is the encrypted text.
  • needs to work with all the visible ASCII characters from space to ~ (ASCII codes of 32 through 126). If the shifted code goes outside of this range, it should wrap around. For example, if we shift ~ by 1, the result should be space. If we shift space by -1, the result should be ~.

This is my MATLAB code:

function [coded] = caesar(input_text, shift)
x = double(input_text); %converts char symbols to double format
for ii = 1:length(x) %go through each element
    if (x(ii) + shift > 126) & (mod(x(ii) + shift, 127) < 32)
        x(ii) = mod(x(ii) + shift, 127) + 32; %if the symbol + shift > 126, I make it 32
    elseif (x(ii) + shift > 126) & (mod(x(ii) + shift, 127) >= 32)
        x(ii) = mod(x(ii) + shift, 127);
    elseif (x(ii) + shift < 32) & (126 + (x(ii) + shift - 32 + 1) >= 32)
        x(ii) = 126 + (x(ii) + shift - 32 + 1);
    elseif (x(ii) + shift < 32) & (126 + (x(ii) + shift - 32 + 1) < 32)
        x(ii) = abs(x(ii) - 32 + shift - 32);
    else x(ii) = x(ii) + shift;
    end
end
     coded = char(x); % converts double format back to char
end

I can't seem to make the wrapping conversions correctly (e.g. from 31 to 126, 30 to 125, 127 to 32, and so on). How should I change my code to do that?


Solution

  • Before you even start coding something like this, you should have a firm grasp of how to approach the problem.

    The main obstacle you encountered is how to apply the modulus operation to your data, seeing how mod "wraps" inputs to the range of [0 modPeriod-1], while your own data is in the range [32 126]. To make mod useful in this case we perform an intermediate step of shifting of the input to the range that mod "likes", i.e. from some [minVal maxVal] to [0 modPeriod-1].

    So we need to find two things: the size of the required shift, and the size of the period of the mod. The first one is easy, since this is just -minVal, which is the negative of the ASCII value of the first character, which is space (written as ' ' in MATLAB). As for the period of the mod, this is just the size of your "alphabet", which happens to be "1 larger than the maximum value, after shifting", or in other words - maxVal-minVal+1. Essentially, what we're doing is the following

    input -> shift to 0-based ("mod") domain -> apply mod() -> shift back -> output
    

    Now take a look how this can be written using MATLAB's vectorized notation:

    function [coded] = caesar(input_text, shift)
    FIRST_PRINTABLE = ' ';
    LAST_PRINTABLE = '~';
    N_PRINTABLE_CHARS = LAST_PRINTABLE - FIRST_PRINTABLE  + 1;
    
    coded = char(mod(input_text - FIRST_PRINTABLE + shift, N_PRINTABLE_CHARS) + FIRST_PRINTABLE);
    

    Here are some tests:

    >> caesar('blabla', 1)
    ans =
        'cmbcmb'
    >> caesar('cmbcmb', -1)
    ans =
        'blabla'
    >> caesar('blabla', 1000)
    ans =
        '5?45?4'
    >> caesar('5?45?4', -1000)
    ans =
        'blabla'