Ad

Details of the algorithm are here:
https://eprint.iacr.org/2017/339.

This was designed to be a human to human encryption algorithm without the need for a computer. I decided to automate it; sue me.

I did notice two quirks with this algotithm:
1) A letter can be encrypted as itself. This happens when the marker is on '#'.
2) The 'marker' does not move when the cipher text is '#'.

class LC4():

    def __init__(self, key):
        """
         Set the indices to zero and puts the key in the state matrix

         (i,j) is the location of the 'marker' in the state matrix
         i is the row, j is the column

         (x,y) is the location of the cipher text character in the
         state matrix.  x is the row, y is the column.

         (r,c) is the location of the plain text character in the
         state matrix.  r is the row, c is the column.

         Allowable characters are each given a value 0-35.

         Read the key (string) into the state matrix filling up each
         row in sequence top to bottom.

         For example:
         [['#', '_', '2', '3', '4', '5'],
          ['6', '7', '8', '9', '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']]
        """

        self.i = 0
        self.j = 0
        self.x = 0
        self.y = 0
        self.r = 0
        self.c = 0

        self.value = '#_23456789abcdefghijklmnopqrstuvwxyz'

        self.state = [[key[(i * 6) + j] for j in range(6)] for i in range(6)]

    def use_nonce(self, nonce):
        """
         The function advances the state using a provided random
         string of characters (at least six) without using any of the
         of the cipher text that is generated.
        """

        for each in nonce:
            self.encrypt_character(each)

    def find_character(self, text):
        """
         Returns the current row, column (r,c) coordinates for a given
         character.
        """

        for r in range(6):
            if text in self.state[r]:
                c = self.state[r].index(text)
                return r, c

    def cycle_state(self, cipher):
        """
         Circular shift the row containing the plain text character to
         the  right.  Note that if the 'marker' is in the row, it
         moves with the row.

         Then circular shift the column containg the cipher text down.
         Note that if the 'marker' is in the column, it moves with the
         column.

         Move the 'marker' based on the value of the cipher text.
        """

        # Shift plain text row right
        self.state[self.r] = self.state[self.r][5:] + self.state[self.r][:5]

        # Update indices as neccessary.
        if self.x == self.r:
            self.y = (self.y + 1) % 6

        if self.i == self.r:
            self.j = (self.j + 1) % 6

        # Shift cipher text column down
        temp = [self.state[n][self.y] for n in range(6)]
        temp = temp[5:] + temp[:5]

        for n in range(6):
            self.state[n][self.y] = temp[n]

        # Update indices as neccessary.
        if self.j == self.y:
            self.i = (self.i + 1) % 6

        # Move the 'marker' position based on the cipher text value.
        cipher_value = self.value.index(cipher)

        self.i = (self.i + (cipher_value // 6)) % 6
        self.j = (self.j + (cipher_value % 6)) % 6

    def encrypt_character(self, character):
        """
         Find the plain text character in the state matrix.

         To find the location of the encrypted text:
         1. Find the value of the character under the 'marker',
         2. Calculate the x, y location of the cipher text from the
            value of the 'marker' and the plain text location.
        """

        self.r, self.c = self.find_character(character)
        value = self.value.index(self.state[self.i][self.j])

        self.x = (self.r + (value // 6)) % 6
        self.y = (self.c + (value % 6)) % 6
        cipher = self.state[self.x][self.y]

        self.cycle_state(cipher)
        return cipher

    def decrypt_character(self, cipher):
        """
         Find the encrypted character in the state matrix

         To find the location of the encrypted text:
         1. Find the value of the character under the 'marker'.
         2. Calculate the r, c location of the plain text from the
            value of the 'marker' and the cipher text location.
        """
        self.x, self.y = self.find_character(cipher)
        value = self.value.index(self.state[self.i][self.j])

        self.r = (self.x - (value // 6)) % 6
        self.c = (self.y - (value % 6)) % 6
        plain_text = self.state[self.r][self.c]

        self.cycle_state(cipher)
        return plain_text

    def encrypt(self, message):
        return ''.join(self.encrypt_character(each) for each in message)

    def decrypt(self, message):
        return ''.join(self.decrypt_character(each) for each in message)