Cryptography - Playfair Cipher



The Playfair cipher, also known as the Playfair square or the Wheatstone-Playfair cipher, is a manual symmetric encryption scheme that was the first that used literal digram substitution. Charles Wheatstone created the technique in 1854, but it is named after Lord Playfair to promote the use of it.

The approach encrypts pairs of letters rather than single letters, as is the case with the simple substitution cipher and the more complex Vigen ere cIpher systems that were previously used.

The Playfair cipher is thus substantially more difficult to break because the frequency analysis used for basic substitution ciphers does not apply to it. Frequency analysis of bigrams is possible, but extremely complex. With 600 possible bigrams rather than 26 possible monograms (single symbols, often letters in this context), a far bigger cipher text is necessary to be functional.

History

The Playfair Cipher is the first and best-known digraph substitution cipher that uses symmetry encryption. Charles Wheatstone created the cipher in 1854, and Lord Playfair, who advocated its use, gave it its name. Unlike a conventional substitution cipher, which only encrypts single letters, the Playfair Cipher approach encodes digraphs or sections of letters.

The Playfair Cipher is fast and requires no additional tools to operate. British and Australian forces used it tactically during World War I, the Second Boer War, and World War II. The primary purpose of the encryption was to protect non-critical yet important data during actual battle. By the time the opposition's cryptanalysts decrypted it, the information was useless.

Understanding the Playfair Cipher

The Playfair Cipher comprises a 5 by 5 matrix of letters (the key table), with no duplicates. The letters I and J are considered the same letter. We create the key table by arranging the unique letters of a keyword in sequence, followed by the remaining letters of the alphabet.

Consider the word SECURITY as an example. First, we record the letters of that phrase in the first squares of a 5 x 5 matrix −

Playfair Cipher

The remaining squares of the matrix are then filled with the remaining alphabet letters, in alphabetical sequence. However, since there are 26 letters and only 25 squares available, we allocate both I and J to the same square.

Playfair Cipher Understanding

When choosing a term, make sure that no letter is duplicated, and especially that the letters I and J do not appear together. Keywords like INJURE, JUICE, and JIGSAW, for example, would be disqualified since they feature both I and J at the same time, which violates this criteria.

Encryption Process

The encryption process of the Playfair cipher consists of a number of steps that convert a message into its encrypted the same.

Create the Key Square

To begin, we will create a key square with a specified keyword. In this example, we will utilise the term SECURITY −

Playfair Cipher encryption

Prepare the Message

Before we can encrypt the message, we must first process it. We treat J as I, so eliminating J from the process of encryption. We also delete any non-alphabetic letters, like spaces and punctuation marks.

For example, processing the string HELLOWORLD gives HELOWORLD.

Pair the Letters

We proceed by breaking the created message into pairs of letters (digraphs). If two successive letters in a digraph are identical, an X is inserted between them. Also, if the plaintext is of odd length, we append X at the end to create a full digraph.

For example, while dealing with the word "HELLO WORLD," we will divide it into pairs of letters −

HE LL OW OR LD

The digraph LL has identical consecutive letters, so we insert X between them −

HE LX LO WO RL D

The message has an unusual length after insertion, therefore we append X at the end of it to make it even −

HE LX LO WO RL DX

Encryption Rules

There are mainly three criterias for encrypting letters within the same pair.

  • If the two letters in the pair are in the same row of the key square, we replace them with the letter to their right.

  • If both letters in the pair are found in the same column of the key square, we will replace each letter with the letter below it.

  • If the letters are in different rows and columns, we form a rectangle with them and change each letter with the letter in the opposite corner.

Using the matrix with the keyword SECURITY, let us find the row and column of every pair and implement the encryption rules to HELLOWORLD whose pairs are −

HE LX LO WO RL DX

After applying the encryption rules to all of the letter pairings, we will obtain FUOQMPXNSPHQ.

Decryption Process

When decrypting a message encrypted with the Playfair Cipher, the method requires reversing the actions used during encryption.

Key Square Building

The decryption method, like the encryption process, begins by creating the key square with the keyword SECURITY. The key square is a key reference grid that helps decrypt the encoded message.

This key square provides the foundation for understanding the encrypted text during decryption.

Ecryption Rules

Decryption rules are just the reverse encryption rules. When both letters in a pair are in the same row of the key square, we replace them with the letter from the left.

Similarly, suppose both letters in the pair are located in the same column of the key square. In that scenario, we replace each letter with the letter immediately above it. When the letters are in separate rows and columns, we use the letter pairs to create a rectangle and replace each letter with the letter in the opposite corner.

Process

Let us decrypt the message FUOQMPXNSPHQ with the help of the above decryption rules. So, we will process them one by one.

F and U are in distinct rows and columns, resulting in a rectangle with corners E, U, F, and H. Exchanging F with its opposite corner H and U with its opposite corner E gives HEOQMPXNSPHQ.

O and Q are in distinct rows and columns, which creates a rectangle with corners L, O, X, and O. Exchanging O with its opposite corner L and Q with its opposing corner X gives HELXMPXNSPHQ.

Continuing this technique produces HELXLOWORLDX. At this moment, we have HELXLOWORLDX. After removing the Xs, we receive HELLOWORLD.

Importance of Playfair Cipher

During World Wars I and II, the Playfair Cipher gained popularity due to its relative complexity in comparison to other ciphers of the period. Furthermore, no specialised equipment or methodologies were required for either data encryption or decryption. However, with the introduction of computers, the Playfair Cipher became outdated since computers could easily break codes to decrypt Playfair Ciphers.

As a result, with the improvement of digital encryption and the passage of time, the Playfair Cipher was no longer an acceptable method of message encoding due to the risk of data falling into hands that were not intended. Therefore, using the Playfair Cipher for businesses is not suggested.

Implementation

The Playfair Cipher works with pairs of letters (digraphs) in the original text (plaintext). It uses a 5x5 key square matrix to encrypt these digraphs. The key matrix is created from a keyword and the English alphabet.

Before encryption, the plaintext is converted to lowercase, spaces are removed, and double letters are separated by a placeholder letter ('x'). The plaintext is then split into digraphs. For each digraph, the corresponding encrypted digraph is found using specific rules based on the positions of the letters in the key matrix.

The encrypted digraphs are then joined together to form the final encrypted message. Both the key and the plaintext can be changed, creating different encryption options.

Example

Below is an implementation of the Playfair Cipher in C, C++, Python and Java −

#include <stdio.h>
#include <string.h>

char alphabet_list[] = "abcdefghiklmnopqrstuvwxyz";

// Function to remove spaces
void remove_spaces(char *text) {
   int i, j = 0;
   for (i = 0; text[i] != '\0'; i++) {
      if (text[i] != ' ') {
         text[j++] = text[i];
      }
   }
   text[j] = '\0';
}

// Function to generate key matrix
void generate_key_matrix(char *key, char matrix[5][5]) {
   char used[26] = {0};
   int i, j, index = 0;

   for (i = 0; key[i] != '\0'; i++) {
      if (!used[key[i] - 'a']) {
         used[key[i] - 'a'] = 1;
         matrix[index / 5][index % 5] = key[i];
         index++;
      }
   }

   for (i = 0; alphabet_list[i] != '\0'; i++) {
      if (!used[alphabet_list[i] - 'a']) {
         used[alphabet_list[i] - 'a'] = 1;
         matrix[index / 5][index % 5] = alphabet_list[i];
         index++;
      }
   }
}

// Function to search for an element in the matrix
void search_element(char matrix[5][5], char element, int *row, int *col) {
   for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 5; j++) {
         if (matrix[i][j] == element) {
            *row = i;
            *col = j;
            return;
         }
      }
   }
}

// Function to encrypt using Playfair rules
void encrypt_playfair_cipher(char matrix[5][5], char *plaintext, char *ciphertext) {
   int len = strlen(plaintext);
   int i, row1, col1, row2, col2;

   for (i = 0; i < len; i += 2) {
      search_element(matrix, plaintext[i], &row1, &col1);
      search_element(matrix, plaintext[i + 1], &row2, &col2);

      if (row1 == row2) {
         ciphertext[i] = matrix[row1][(col1 + 1) % 5];
         ciphertext[i + 1] = matrix[row2][(col2 + 1) % 5];
      } else if (col1 == col2) {
         ciphertext[i] = matrix[(row1 + 1) % 5][col1];
         ciphertext[i + 1] = matrix[(row2 + 1) % 5][col2];
      } else {
         ciphertext[i] = matrix[row1][col2];
         ciphertext[i + 1] = matrix[row2][col1];
      }
   }
   ciphertext[len] = '\0';
}

int main() {
   char plaintext[] = "tutorialspoint";
   char key[] = "bestkey";
   char ciphertext[50];
   char matrix[5][5];

   remove_spaces(plaintext);
   generate_key_matrix(key, matrix);
   encrypt_playfair_cipher(matrix, plaintext, ciphertext);

   printf("The Key text: %s\n", key);
   printf("The Plain Text: %s\n", plaintext);
   printf("The CipherText: %s\n", ciphertext);

   return 0;
}

Output

The output obtained is as follows −

The Key text: bestkey
The Plain Text: tutorialspoint
The CipherText: bxeqpmdhcwphqb
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// Function to convert the string to lowercase
void to_lowercase(string &text) {
   for (char &c : text)
      c = tolower(c);
}

// Function to remove all spaces in a string
void remove_spaces(string &text) {
   text.erase(remove(text.begin(), text.end(), ' '), text.end());
}

// Function to group characters into digraphs
vector<string> group_characters(string text) {
   vector<string> groups;
   int group_start = 0;
   for (int i = 2; i < text.length(); i += 2) {
      groups.push_back(text.substr(group_start, 2));
      group_start = i;
   }
   groups.push_back(text.substr(group_start));
   return groups;
}

// Function to fill repeated letters in digraphs
string fill_letter(string text) {
   int k = text.length();
   if (k % 2 == 0) {
      for (int i = 0; i < k; i += 2) {
         if (text[i] == text[i + 1]) {
            text.insert(i + 1, "x");
            return fill_letter(text);
         }
      }
   } else {
      for (int i = 0; i < k - 1; i += 2) {
         if (text[i] == text[i + 1]) {
            text.insert(i + 1, "x");
            return fill_letter(text);
         }
      }
   }
   return text;
}

// Function to generate 5x5 key matrix
vector<vector<char>> generate_key_matrix(string key, string alphabet) {
   vector<char> key_letters;
   for (char c : key)
      if (find(key_letters.begin(), key_letters.end(), c) == key_letters.end())
         key_letters.push_back(c);

   vector<char> matrix_elements = key_letters;
   for (char c : alphabet)
      if (find(matrix_elements.begin(), matrix_elements.end(), c) == matrix_elements.end())
         matrix_elements.push_back(c);

   vector<vector<char>> matrix(5, vector<char>(5));
   int index = 0;
   for (int i = 0; i < 5; i++)
      for (int j = 0; j < 5; j++)
         matrix[i][j] = matrix_elements[index++];

   return matrix;
}

// Function to find letter position in the matrix
pair<int, int> search_element(vector<vector<char>> &matrix, char element) {
   for (int i = 0; i < 5; i++)
      for (int j = 0; j < 5; j++)
         if (matrix[i][j] == element)
            return {i, j};
   return {-1, -1};
}

// Encryption using Row Rule
pair<char, char> encrypt_row_rule(vector<vector<char>> &matrix, int e1_row, int e1_col, int e2_row, int e2_col) {
   char char1 = matrix[e1_row][(e1_col + 1) % 5];
   char char2 = matrix[e2_row][(e2_col + 1) % 5];
   return {char1, char2};
}

// Encryption using Column Rule
pair<char, char> encrypt_column_rule(vector<vector<char>> &matrix, int e1_row, int e1_col, int e2_row, int e2_col) {
   char char1 = matrix[(e1_row + 1) % 5][e1_col];
   char char2 = matrix[(e2_row + 1) % 5][e2_col];
   return {char1, char2};
}

// Encryption using Rectangle Rule
pair<char, char> encrypt_rectangle_rule(vector<vector<char>> &matrix, int e1_row, int e1_col, int e2_row, int e2_col) {
   char char1 = matrix[e1_row][e2_col];
   char char2 = matrix[e2_row][e1_col];
   return {char1, char2};
}

// Encrypting text using the Playfair Cipher
string encrypt_playfair_cipher(vector<vector<char>> &matrix, vector<string> &plaintext_list) {
   string cipher_text;
   for (string digraph : plaintext_list) {
      char char1, char2;
      auto [ele1_x, ele1_y] = search_element(matrix, digraph[0]);
      auto [ele2_x, ele2_y] = search_element(matrix, digraph[1]);

      if (ele1_x == ele2_x)
         tie(char1, char2) = encrypt_row_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y);
      else if (ele1_y == ele2_y)
         tie(char1, char2) = encrypt_column_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y);
      else
         tie(char1, char2) = encrypt_rectangle_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y);

      cipher_text += char1;
      cipher_text += char2;
   }
   return cipher_text;
}

// Main function
int main() {
   string alphabet = "abcdefghiklmnopqrstuvwxyz"; // 'j' is usually omitted in Playfair Cipher
   string text_plain = "tutorialspoint";
   string key = "bestkey";

   cout << "The Key text: " << key << endl;
   to_lowercase(text_plain);
   remove_spaces(text_plain);

   string formatted_text = fill_letter(text_plain);
   vector<string> plaintext_list = group_characters(formatted_text);
   if (plaintext_list.back().length() != 2)
      plaintext_list.back() += 'z';

   to_lowercase(key);
   vector<vector<char>> matrix = generate_key_matrix(key, alphabet);

   cout << "The Plain Text: " << text_plain << endl;
   string cipher_text = encrypt_playfair_cipher(matrix, plaintext_list);
   cout << "The CipherText: " << cipher_text << endl;

   return 0;
}

Output

The output produced is as follows −

The Key text: bestkey
The Plain Text: tutorialspoint
The CipherText: bxeqpmdhcwphqb
import java.util.*;

public class PlayfairCipher {
   static final String ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ";

   // Function to preprocess text (convert to lowercase, remove spaces, replace 'j' with 'i')
   static String preprocessText(String text) {
      text = text.toUpperCase().replaceAll(" ", "").replace('J', 'I');
      return text;
   }

   // Function to insert 'X' between duplicate letters in digraphs
   static String prepareDigraphs(String text) {
      StringBuilder newText = new StringBuilder(text);
      for (int i = 0; i < newText.length() - 1; i += 2) {
         if (newText.charAt(i) == newText.charAt(i + 1)) {
            newText.insert(i + 1, 'X');
         }
      }
      if (newText.length() % 2 != 0) {
         newText.append('Z'); // Append 'Z' if length is odd
      }
      return newText.toString();
   }

   // Function to create a 5x5 key matrix
   static char[][] generateKeyMatrix(String key) {
      Set<Character> used = new LinkedHashSet<>();
      for (char c : key.toUpperCase().toCharArray()) {
         if (!used.contains(c) && ALPHABET.indexOf(c) != -1) {
            used.add(c);
         }
      }
      for (char c : ALPHABET.toCharArray()) {
         if (!used.contains(c)) {
            used.add(c);
         }
      }
      char[][] matrix = new char[5][5];
      Iterator<Character> it = used.iterator();
      for (int i = 0; i < 5; i++) {
         for (int j = 0; j < 5; j++) {
            matrix[i][j] = it.next();
         }
      }
      return matrix;
   }

   // Function to find the row and column of a letter in the matrix
   static int[] searchElement(char[][] matrix, char letter) {
      for (int i = 0; i < 5; i++) {
         for (int j = 0; j < 5; j++) {
            if (matrix[i][j] == letter) {
               return new int[]{i, j};
            }
         }
      }
      return null; // Should never happen if input is valid
   }

   // Function to encrypt digraphs using Playfair rules
   static String encryptText(String text, char[][] matrix) {
      StringBuilder cipherText = new StringBuilder();
      for (int i = 0; i < text.length(); i += 2) {
         char char1 = text.charAt(i);
         char char2 = text.charAt(i + 1);
         int[] pos1 = searchElement(matrix, char1);
         int[] pos2 = searchElement(matrix, char2);

         if (pos1 == null || pos2 == null) { // Safety check
            throw new IllegalArgumentException("Invalid character found in text.");
         }

         if (pos1[0] == pos2[0]) { // Same row
            cipherText.append(matrix[pos1[0]][(pos1[1] + 1) % 5]);
            cipherText.append(matrix[pos2[0]][(pos2[1] + 1) % 5]);
         } else if (pos1[1] == pos2[1]) { // Same column
            cipherText.append(matrix[(pos1[0] + 1) % 5][pos1[1]]);
            cipherText.append(matrix[(pos2[0] + 1) % 5][pos2[1]]);
         } else { // Rectangle rule
            cipherText.append(matrix[pos1[0]][pos2[1]]);
            cipherText.append(matrix[pos2[0]][pos1[1]]);
         }
      }
      return cipherText.toString();
   }

   // Main function
   public static void main(String[] args) {
      String key = "BESTKEY";
      String text = "TUTORIALSPOINT";

      System.out.println("The Key text: " + key);
      text = preprocessText(text);
      text = prepareDigraphs(text);

      char[][] matrix = generateKeyMatrix(key);
      System.out.println("The Plain Text: " + text);

      String cipherText = encryptText(text, matrix);
      System.out.println("The CipherText: " + cipherText);
   }
}

Output

The output obtained is as shown below −

The Key text: BESTKEY
The Plain Text: TUTORIALSPOINT
The CipherText: BXEQPMDHCWPHQB
# List of alphabets
alphabet_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

# Function to convert the string to lowercase
def to_lowercase(text):
   return text.lower()

# Function to remove all spaces in a string
def remove_spaces(text):
   new_text = ""
   for char in text:
      if char != " ":
         new_text += char
   return new_text

# Function to group 2 elements of a string
def group_characters(text):
   groups = []
   group_start = 0
   for i in range(2, len(text), 2):
      groups.append(text[group_start:i])
      group_start = i
   groups.append(text[group_start:])
   return groups

# Function to fill a letter in a string element
def fill_letter(text):
   k = len(text)
   if k % 2 == 0:
      for i in range(0, k, 2):
         if text[i] == text[i+1]:
            new_word = text[0:i+1] + str('x') + text[i+1:]
            new_word = fill_letter(new_word)
            break
         else:
            new_word = text
   else:
      for i in range(0, k-1, 2):
         if text[i] == text[i+1]:
            new_word = text[0:i+1] + str('x') + text[i+1:]
            new_word = fill_letter(new_word)
            break
         else:
            new_word = text
   return new_word

# Generating the 5x5 key square matrix
def generate_key_matrix(word, alphabet_list):
   key_letters = []
   for char in word:
      if char not in key_letters:
         key_letters.append(char)

   complementary_elements = []
   for char in key_letters:
      if char not in complementary_elements:
         complementary_elements.append(char)
   for char in alphabet_list:
      if char not in complementary_elements:
         complementary_elements.append(char)

   matrix = []
   while complementary_elements != []:
      matrix.append(complementary_elements[:5])
      complementary_elements = complementary_elements[5:]

   return matrix

# Searching for an element in the matrix
def search_element(matrix, element):
   for i in range(5):
      for j in range(5):
         if matrix[i][j] == element:
            return i, j

# Encryption using Row Rule
def encrypt_row_rule(matrix, e1_row, e1_column, e2_row, e2_column):
   char1 = ''
   if e1_column == 4:
      char1 = matrix[e1_row][0]
   else:
      char1 = matrix[e1_row][e1_column+1]

   char2 = ''
   if e2_column == 4:
      char2 = matrix[e2_row][0]
   else:
      char2 = matrix[e2_row][e2_column+1]

   return char1, char2

# Encryption using Column Rule
def encrypt_column_rule(matrix, e1_row, e1_column, e2_row, e2_column):
   char1 = ''
   if e1_row == 4:
      char1 = matrix[0][e1_column]
   else:
      char1 = matrix[e1_row+1][e1_column]

   char2 = ''
   if e2_row == 4:
      char2 = matrix[0][e2_column]
   else:
      char2 = matrix[e2_row+1][e2_column]

   return char1, char2

# Encryption using Rectangle Rule
def encrypt_rectangle_rule(matrix, e1_row, e1_column, e2_row, e2_column):
   char1 = matrix[e1_row][e2_column]
   char2 = matrix[e2_row][e1_column]

   return char1, char2

# Encrypting text using the Playfair Cipher
def encrypt_playfair_cipher(matrix, plaintext_list):
   cipher_text = []
   for i in range(0, len(plaintext_list)):
      char1 = 0
      char2 = 0
      ele1_x, ele1_y = search_element(matrix, plaintext_list[i][0])
      ele2_x, ele2_y = search_element(matrix, plaintext_list[i][1])

      if ele1_x == ele2_x:
         char1, char2 = encrypt_row_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y)
      elif ele1_y == ele2_y:
         char1, char2 = encrypt_column_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y)
      else:
         char1, char2 = encrypt_rectangle_rule(matrix, ele1_x, ele1_y, ele2_x, ele2_y)

      cipher = char1 + char2
      cipher_text.append(cipher)
   return cipher_text

# Main function
text_plain = 'tutorialspoint'
text_plain = remove_spaces(to_lowercase(text_plain))
plaintext_list = group_characters(fill_letter(text_plain))
if len(plaintext_list[-1]) != 2:
   plaintext_list[-1] = plaintext_list[-1]+'z'

key = "bestkey"
print("The Key text:", key)
key = to_lowercase(key)
matrix = generate_key_matrix(key, alphabet_list)

print("The Plain Text:", text_plain)
cipher_list = encrypt_playfair_cipher(matrix, plaintext_list)

cipher_text = ""
for i in cipher_list:
   cipher_text += i
print("The CipherText:", cipher_text)

Output

Following is the output of the above code −

The Key text: bestkey
The Plain Text: tutorialspoint
The CipherText: bxeqpmdhcwphqb

Advantages

  • If we closely examine the algorithm, we can see that each stage of the process produces a unique ciphertext, making it more difficult for the cryptanalyst.

  • It is insensitive to brute force attacks.

  • Cryptanalysis is impossible (decoding a cipher without knowing the key).

  • Removes a flaw in the simple Playfair square cipher.

  • Making the substitution is simple.

Disadvantages

The Playfair Cipher has disadvantages as the following −

  • Only twenty-five alphabets are supported.

  • It is incompatible with characters of that number.

  • Only capital and lowercase letters are acceptable.

  • Special characters such as spaces, newlines, and punctuation are not permitted.

Summary

The Playfair Cipher is considered as one of the oldest and most effective methods of data encryption. A strong understanding of the Playfair Cipher serves as the foundation for data encryption and machine learning.

Advertisements