Search code examples
pythonlistencryptionvigenere

Encrypting a string using a key and vigenere cipher already generated by another function


I've been having trouble figuring out how to code and implement a couple functions in my program. The encrypt method that calls the get_col_index and get_row_index methods, and also the get_col_index and get_row_index methods. The encrypt method is called by the main method, and the message, key, and vignere_square is passed through. The Vignere Matrix cipher is already generated in a separate function. The encrypt method then calls the get_col_index and get_row_index methods to encrypt the message, skipping over everything that isn't a letter.

A big part of my problem has been figuring out how to encode the message using the get_row_index and get_col_index methods. for get_col_index, we pass through the message and vigenere square, and it returns the column index of each letter in the message. For the get_row_index, we pass through the key and vigenere square, and it returns the row index for each letter of the key.. I've been trying to use for loops for these functions, and I usually get back the message(not encrypted), a list with random letters, or errors. Here is my latest attempt, I tried using an append in the encrypt method but it just adds the message and key into a list.

def main():
    vig_square = create_vig_square()
    message = input("Enter a multi-word message with punctuation: ")
    input_key = input("Enter a single word key with no punctuation: ")
    msg = message.lower()
    key = input_key.lower()
    print("The encoded message is: ",encrypt(msg, key, vig_square))
    print("The decoded message is: ")

def encrypt(msg, key, vig_square):
    msg_char = msg
    key_char = key
    coded_msg = list()
    coded_msg.append(get_col_index(msg_char, vig_square))
    coded_msg.append(get_row_index(key_char, vig_square))
    return coded_msg

def get_col_index(msg_char, vig_square):
    column_index = ""
    for i in range(len(vig_square)):
        column_index = msg_char
        i += 1
    return column_index

def get_row_index(key_char, vig_square):
    row_index =  ""
    for i in range(len(vig_square)):
        row_index = key_char
        i += 1
    return row_index


def create_vig_square():
    vig_square = list()
    for row in range(26):
        next_row = list()
        chr_code = ord('a') + row
        for col in range(26):
            letter = chr(chr_code)
            next_row.append(letter)
            chr_code = chr_code + 1
            if chr_code > 122:
                chr_code = ord('a')
        vig_square.append(next_row)
    return vig_square

main()

I expect output to be like this:

The eagle has landed.

Enter a single word key with no punctuation:
LINKED

The encoded message is:
epr oejwm ukw olvqoh.

The decoded message is:
the eagle has landed.

Solution

  • The vignere cipher has some nice properties, given that the vignere square is cyclical, both horizontally and vertically, which means that the lookup can be done as so:

    # assume col and row are lowercase letters
    def vignere_lookup(col, row):
      base = ord('a')
      index = ((ord(col) - base) + (ord(row) - base)) % 26
      return chr(base + index)
    

    This assigns the letters a-z to the numbers 0-25, adds the columns and row letter, and uses modulo to go around in case the value is higher than 'z'(25)

    This means encryption can be done with:

    def encrypt(message, key):
      ciphertext = ''
      key_index = 0
      # convert to lower case
      for letter in message.lower():
        # only encrypt the letters a-z, for space, punctuation, numbers, just add the raw letter
        if ord(letter) in range(ord('a'), ord('z') + 1):
          # make sure to lowercase the key as well
          ciphertext += vignere_lookup(letter, key[key_index % len(key)].lower())
          key_index += 1
        else:
          ciphertext += letter
    
      return ciphertext
    

    Similarly, the decryption lookup can be done using subtraction, taking advantage of the fact that in python, using modulo on negative numbers cycle as we would hope. This gives the decryption as:

    def vignere_reverse(col, target):
      base = ord('a')
      # notice the subtraction instead of addition
      index = ((ord(col) - base) - (ord(target) - base)) % 26
      return chr(base + index)
    
    def decrypt(cipher, key):
      message = ''
      key_index = 0
      for letter in cipher.lower():
        if ord(letter) in range(ord('a'), ord('z') + 1):
          message += vignere_reverse(letter, key[key_index % len(key)].lower())
          key_index += 1
        else:
          message += letter
    
      return message
    

    Put all of this together and you can do:

    cipher = encrypt("The eagle has landed.", "LINKED")
    print(cipher) # prints "epr oejwm ukw olvqoh."
    decrypted = decrypt(cipher, "LINKED")
    print(decrypted) # prints "the eagle has landed."