Sunday, 17 April 2022

A simple cipher with randomness to deter breaking it - The cipher used in The Cipher (short story)

This is the code used in The Cipher (short story) which was published in this blog. It is a very simple cipher. You can read the story in question here: chapter 1  | chapter 2 |  chapter 3 | chapter 4

Hopefully, this is a fun coding exercise for you.

To cipher:

  1. You can use only letters and space. Any other characters will have to be changed into these (or the code changed to accommodate them, which is trivial).
  2. Take the plaintext one letter at a time. Convert it to a number (A-Z corresponding to 0-25, space = 26).
  3. Generate a random single byte hexadecimal number
  4. Generate a second number so that the modulus 27 of the sum of the two numbers gives the number that corresponds to the character you want to cipher. Mathematically: (random_number_1 + random_number_2) mod 27 = the numerical equivalent of the character to want to encipher. We'll see the detail of the maths in the code below.
  5. That is your ciphertext. Repeat for every letter in the cipher.

A graphical representation of the enciphering method mentioned above.

To decipher:

  1. Take two hexadecimal numbers out of the cipher (four characters, break it into blocks of two characters each)
  2. convert them to the usual denary numbers
  3. Add the numbers, take the modulus 27 of the sum
  4. Match to corresponding letter / space
  5. This is your plaintext. Repeat through the entire ciphertext.

A graphical representation of the deciphering algorithm above.


The code:

The code is given in Java this time. Converting it to C++ is easy, as all the methods used are static, so you can put everything into one file without issue. Alternatively, you can call the method from another class as well. Either way should work.

Class Cipher:

This class handles the basic cipher operations. It accepts a line of ciphered text and deciphers it, or vice versa. I have left all the functions public, but it is not necessary. You can leave just the cipherLine and decipherLine methods public and make the rest private, it should work just fine.

I've also made all the functions static. You can go ahead and make the class static as well, because you really don't need to instantiate it. Alternatively, you could remove the static keyword from all the methods and use an instance of the class Cipher in main, but it's pointless as there is no need to instantiate it.


import java.util.Random;

public class Cipher {
private static Random rd = new Random();

//To store alphabet of cipher
private static char[] letters1 = {'A','B','C','D','E','F','G','H','I','J',
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',' '};
//To store alphabet, lower case. The cipher can translate from upper case or
//lower case as programmed, but the deciphered result will be upper case.
private static char[] letters2 = {'a','b','c','d','e','f','g','h','i','j',
'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' '};

/*
Ciphers a line of plaintext.
Returns the ciphered line.
*/
public static String cipherLine(String line){
String ciphered = "";
for (int i = 0; i<line.length(); i++){
ciphered = ciphered+encipher(line.charAt(i));
}
System.out.println(ciphered.length());
return ciphered;
}

/*
Ciphers a single character
Returns the ciphered equivalent of the character
*/
public static String encipher(char letter){
String ciphered = "";
int num = 0;
int rem1 = 0;

//get equivalent numerical value
num = mapNum(letter);

//If number isn't mapped, that character is skipped over.
if ((num>26)||(num<0)){
ciphered = "";
System.out.println("invalid input");
return ciphered;
}

//Generates the two numbers whose sum produces the modulus 27 value we want.
//If you want an alphabet with over 27 characters, you can replace the value here.
int num1, num2 = 0;
num1 = rd.nextInt(256);
rem1 = num1%27;
if (num>rem1){
num2 = ((rd.nextInt(8)*27+(num-rem1)));
}
else if (num==rem1){
num2 = ((rd.nextInt(8)*27));
}
else{
num2 = ((rd.nextInt(7)*27+(27-rem1)+num));
}

String temp1, temp2;
temp1 = Integer.toHexString(num1); //Convert to hex
if (temp1.length()==1) temp1 = "0"+temp1; //Pad if it is not two characters
temp2 = Integer.toHexString(num2);
if (temp2.length()==1) temp2 = "0"+temp2;

ciphered = temp1+temp2;

return ciphered;
}

/*
Maps a letter to a corresponding number (index of array).
Works with both upper and lower case characters.
Returns corresponding number.
*/
public static int mapNum (char letter){
int num = 0;
for (int i =0; i<27; i++){
if ((letter==letters1[i])||(letter==letters2[i])){
num = i;
break;
}
else{
num = 28;
}
}

return num;
}

/*
Deciphers a line of enciphered text.
Returns the plaintext of that line.
*/
public static String decipherLine(String line){
String deciphered = "";
char letter ='0';
for (int i = 0; i<(line.length()/4); i++){
letter = decipher(line.substring(4*i, 4*(i+1)));
deciphered = deciphered+letter;
}
return deciphered;
}

/*
Deciphers a pair of hexadecimal numbers (or four characters).
Returns one plaintext characters.
*/
public static char decipher (String letters){
int num,num1,num2 = 0;
char letter;
if (letters.length()!=4){
System.out.println("wrong length for character" + letters);
return '0';
}
String temp1 = letters.substring(0,2);
String temp2 = letters.substring(2,4);

num1 = Integer.parseInt(temp1,16);
num2 = Integer.parseInt(temp2,16);


num = (num1+num2)%27;

letter = mapChar(num);

return letter;
}

/*
Maps a number to a character. Simply look up value in array if within range.
Returns character corresponding to number.
*/
public static char mapChar(int num){
char letter = '0';
if ((num<0)||(num>26)){
System.out.println("Invalid number");
return letter;
}
letter = letters1[num];
return letter;
}
}

The breakdown:

Importing Random: because we're using random numbers to add a level of randomness when we're enciphering it.

Then within the class, random is initialised. 

The two arrays letters1 and letters2 are used to store the letters / characters used in the cipher.

Function cipherLine takes in one line of text, extracts a character from it at a time, and enciphers it.

The function encipher takes in a character sent to it by cipherLine, and ciphers it. This is where bulk of the processing happens. First, the number corresponding to the letter is obtained from mapNum. Then the return value is checked, and if it is not in range, the character is simply not ciphered. If it is in range (0-26 for mod 27, for 27 characters in the alphabet (letters + space)) Then it proceeds to generated the ciphered values.

This is done by generating a random number under 256 (as we want it to fit to 8 bits or two hexadecimal characters). This is the first number, num1 . The next part is generating the second number (num2) so that you get the required result by (num1 + num2) mod 27. This also includes a random component. Random numbers from 0-7 are used because 0 to 7*27 is 0 to 189, which will keep you just below the ceiling of 255 even if you add 27 to it.

The calculation basically is,

  1. First, calculate first number (num1in the code) modulus 27. rem = num1 mod 27. This value is used in the next part of the calculation.
  2. Then compare it with the remainder you need, i.e., the value that mapNum gave you. This is called num in the code.
  3. If rem is greater, simply calculate the difference (rem - num). Tack that on top of an integer multiple of 27 (the integer 0-7 generated randomly, multiplied by 27) and assign it to num2, you're good to go. In equation form: num2 = (random integer * 27) + (rem - num).
  4. If the two are equal, simply assign an integer multiple of 27 to num2. That is, num2 = (random integer * 27).
  5. If num is greater, calculate (27 - rem) + num, add that to an integer multiple of 27. However, note that you can't use a multiple of 7 here, as you may go over 255 in that case. That is why 0 to 6 is used. In equation form: num2 = (random integer *27) + (27 - rem) + num.

Once this is done, you have two numbers under 255, whose sum mod 27 gives you the index of your plaintext. These numbers are still integers, so the function Integer.toHexString() is used to convert it to a hexadecimal number. Note that the output is a string and not a number, which is actually convenient for us.

Next you have to check whether it's a single character, and if that's the case, you need to pad it with a zero, otherwise it'll affect the length of our string. This is the set of four characters is returned.

The next function mapNum is also fairly straightforward. You check whether the character you want is in the alphabet. If it is, return its index, if not, return 28 which the function encipher reads as an error.

Next we get to the functions related to deciphering.

decipherLine takes a string, and feeds four characters into the decipher function at a time. I used substring to do this. 

decipher is the function where most of the calculation takes place here as well. First, it checks whether the incoming data is four characters long. if it isn't, it's not decoded. However, there is a problem here in that it doesn't check whether it's a valid hexadecimal number. You can try adding this part yourself.

From then on, it's simple, use substring to break it into two numbers of length 2 each, then convert both to numbers using Integer.parseInt(temp1, 16). The second argument, 16, tells the program that it's a hexadecimal number.

Then you add the two numbers together and get the modulus, which gives you the index of the character you want.

The next function mapChar, does exactly what its name says - gets the number, check if it's in range and get the corresponding letter from the alphabet.

Class Main:

This one is mostly housekeeping: selecting mode (cipher or decipher), telling the program where to get the ciphertext / plaintext, and feeding in a line of the input to the cipher or decipher function.


import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {

public static void main(String[] args) {

// For user input
char inChar = '\0';
//Whether cipher or decipher mode
int mode = -1;

//For reading input
Scanner sc = new Scanner (System.in);

//Cipher or decipher mode: user input
System.out.println("Select C for enciphering, and D for deciphering");
inChar = sc.next().charAt(0);

if ((inChar=='c')||(inChar=='C')){
System.out.println("Ciphering mode");
mode = 0;
}
else if ((inChar=='d')||(inChar=='D')){
System.out.println("Deciphering mode");
mode = 1;
}

//Ciphering data:
if (mode==0){
String ciphered = "";
//For reading from file
try{
File myFile = new File("input1.txt");
Scanner myReader = new Scanner(myFile);
while (myReader.hasNextLine()){
String line = myReader.nextLine();
//Call the cipherLine method from Cipher to get ciphertext
ciphered = ciphered + Cipher.cipherLine(line) + "\n";
}
System.out.println(ciphered);
} catch (FileNotFoundException e) {
e.printStackTrace();
System.exit(0);
}
}
else if (mode ==1){
String deciphered = "";
//For reading from file
try{
File myFile = new File("input2.txt");
Scanner myReader = new Scanner(myFile);
while (myReader.hasNextLine()){
String line = myReader.nextLine();
//Call the decipherLine method from Cipher to get plaintext
deciphered = deciphered + Cipher.decipherLine(line) + "\n";
}
System.out.println(deciphered);
} catch (FileNotFoundException e) {
e.printStackTrace();
System.exit(0);
}
}

}
}

The breakdown:

Imports: Java.io.File and Java.io.FileNotFoundException are for reading from file. If you're going to hardcode the ciphertext / plaintext or if it's going to be user input, this is not necessary. Java.util.Scanner is used for both reading user input and reading files.

The first few variables are explained in the code itself.

Next, it asks the user whether to encipher or decipher. If the user input is c for encipher or d for decipher, it runs the respective program (using the mode variable to remember it), otherwise it simply ends. Maybe you can add a message there to let the user know this is happening.

If the mode is 0 (enciphering mode), it reads the input from the selected file line by line, and feeds into encipherLine from the Cipher class, and tacks it onto a string which is then printed once it is done processing the file. You can modify this to output to a file if you want to (which is probably more convenient if you're dealing with a lot of text).

If the mode is 1 (deciphering mode), the process is exactly the same except decipherLine is called.


In conclusion

This cipher is a bit bloated and it's not very efficient, but it's a fun exercise. The biggest advantage here is a result of the bloat, that is, the use of random numbers in the ciphering process makes it nearly immune to most conventional deciphering approaches. 

However, it is not a safe cipher, because once you know the method, you don't really need a key. Another problem is that the length of the message increases by a factor of four which will be problem if you want to cipher a really long message. It could be worse (it could be exponential), but this situation isn't exactly ideal.

Maybe you can also try modifying this to make a new, more secure cipher. Or you could come up with something completely different just for the fun of it. Anyway, I hope you have fun with it.

If you have anything to add, or if you want me to clarify anything, please comment below. I will reply as soon as I can.


You can also follow me on Facebook here.

Until next time!


No comments:

Post a Comment

How to write a character who is smarter than you

We all have that one character (or few) who is significantly smarter than the writer. So, as a writer, how do you write such a character con...