Search code examples
pythonpython-3.xencryptionwhitespacevigenere

Making Vigenére encryption / decryption skip whitespace


I have made a menu based encryption tool using Vigenére cipher. As of now the program encrypts white spaces, how can I get the program to skip over white spaces.

#creating variables to be used
text_in_use = ''
encrypt_key = ''
decrypt_key = ''

#function to encypypt input text
def encrypt(plaintext, key):
    keyLength = len(key)
    keyAsIntegers = [ord(i) for i in key] #create list with the ASCII value for each charachter in key
    plaintextAsIntegers = [ord(i) for i in plaintext] #create list with the ASCII value for each charachter in text
    encyptedtext = '' 
    for i in range(len(plaintextAsIntegers)): #
        encryptvalue = (plaintextAsIntegers[i] + keyAsIntegers[i % keyLength]) % 26 #execute encryption or characters according to vigenere definition
        encyptedtext += chr(encryptvalue + 65)
    return encyptedtext #return the encyptes tex

#function to decrypt the encrypted text
def decrypt(encyptedtext, key):
    keyLength = len(key)
    keyAsIntegers = [ord(i) for i in key] #create list with the ASCII value for each charachter in key
    encryptedTextAsIntegers = [ord(i) for i in encyptedtext] #create list with the ASCII value for each charachter in text
    plaintext = ''
    for i in range(len(encryptedTextAsIntegers)):
        value = (encryptedTextAsIntegers[i] - keyAsIntegers[i % keyLength]) % 26 #decryption of encrypted characters
        plaintext += chr(value + 65)
    return plaintext #return decrypted text

#check if user input is valid
def check_value(userEntry):
    while True:
        try: #check if userinput is an integer
            userInput = int(input(userEntry))
            if userInput not in range(1,6): #check if userinput is in valid range
                print("Invalid choice, valid choices are 1-5! Try again! \n")
        except ValueError:
            print("Invalid choice! Input can't be empty or a string! \n")
            print("""1: Input text to work with
2: Print the current text
3: Encrypt the current text
4: Decrypt the current text
5: Exit""")
        else:
            return userInput #return valid userinput


def menu():
    while True:
        print("""1: Input text to work with
2: Print the current text
3: Encrypt the current text
4: Decrypt the current text
5: Exit""")

        choice = check_value("Enter Choice: ")

        if choice == 1: #allows user to input text for use
            text_in_use = str(input("Enter Text: ")).upper()
            print("Text is set to:",text_in_use,"\n")
        elif choice == 2: #prints set text
            print("Your text:",text_in_use,"\n") 
        elif choice == 3: #ask user to set encryptionkey
            encrypt_key = str(input("Enter an encryptionkey: ")).upper()
            text_in_use = encrypt(text_in_use, encrypt_key)
            print("Your text:", text_in_use)
        elif choice == 4: #ask user for decryptionkey
            decrypt_key = str(input("Enter a the decryptionkey: ")).upper()
            text_in_use = decrypt(text_in_use, decrypt_key)
            print("Your text:", text_in_use)
        elif choice == 5:
            exit()

menu()

I want the program to work as it already does but it should skip whitespaces in the encryption.

Such as:

"HELLO MY MAN" --> encryption(key = asd) --> "HWOLG MQ MSQ"

In other words, the white spaces should still be there in the encrypted text.


Solution

  • Not sure how you got "HWOLG MQ MSQ" when the plaintext is "HELLO MY MAN" and the key is "asd". I'm getting something else.

    In any case, maybe something like this:

    def encrypt(plaintext, key):
        from itertools import cycle
        from string import ascii_uppercase as alphabet
    
        offset = ord("A")
    
        key_char = cycle(key)
    
        encrypted_plaintext = ""
        for char in plaintext:
            # If the current character is any kind of whitespace...
            if char.isspace():
                # Append it to the final string with no changes.
                encrypted_plaintext += char
            else:
                # The character is not whitespace, so you have to encrypt it.
                char_sum = ord(char) + ord(next(key_char))
                char_sum_wrapped = char_sum % len(alphabet)
                encrypted_plaintext += chr(char_sum_wrapped + offset)
        return encrypted_plaintext
    

    If the current character is whitespace, just append it to the final string without any changes. str.isspace returns true if every character in the string (current character) is some kind of whitespace (space, tab, newline, carriage return, etc).

    I try to avoid working with indecies and hardcoded numbers whenever I can, so I changed some things. For example, instead of turning all the characters in the plaintext and key into integers before doing anything else, like you did, I just convert the characters in the loop. Incidentally, the loop is different too - I'm iterating over the characters in the plaintext, as opposed to doing a range-based for loop, and then treating i as an index to the current character. The rest is basically the same (except for the key_char and itertools.cycle stuff, read my notes below).

    Another thing to note is that with this implementation, the key_char iterator will only advance if the current character in the plaintext is not whitespace - however, you may want it to advance regardless. Just something to keep in mind.

    Nevermind, it seems this is the desired behavior for this cipher.

    Also, just a note, your program starts with these few lines:

    #creating variables to be used
    text_in_use = ''
    encrypt_key = ''
    decrypt_key = ''
    

    They don't contribute at all, you can safely remove them.

    EDIT - Some more information:

    itertools.cycle is a function which, given an iterable (like a string or a list), returns an iterator that yields the elements in that iterable. For example:

    >>> from itertools import cycle
    >>> char_iterator = cycle("ABC")
    >>> next(char_iterator)
    'A'
    >>> next(char_iterator)
    'B'
    >>> next(char_iterator)
    'C'
    >>> next(char_iterator)
    'A'
    >>> next(char_iterator)
    'B'
    >>> next(char_iterator)
    'C'
    >>> next(char_iterator)
    'A'
    >>> 
    

    As you can see, the cycle repeats indefinitely. I chose to use itertools.cycle for this reason to replace keyAsIntegers[i % keyLength] in your original code.

    string.ascii_uppercase is just a string consisting of all uppercase letters between A-Z. In my code I import ascii_uppercase and in the same line rename it to alphabet, but they are the same.

    >>> from string import ascii_uppercase as alphabet
    >>> alphabet
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    >>>