2

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • The whole purpose of encryption is to make the cyphertext look as random as possible. If you keep the spaces, it'll be easier to crack. Anyhow, the regular algorithm will encrypt the whitespace as well, so you should either modify the very algorithm or encrypt each word individually – ForceBru Oct 06 '19 at 12:44
  • There are a few dupes of this, but this one is really nicely worded, and the others seem much more mucky with bad code / additional questions / more specific code specific issues and are often not answered, so kudo's for that. Please however take special care in getting the title right and review your post before posting. – Maarten Bodewes Oct 06 '19 at 13:11

2 Answers2

1

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'
>>> 
Paul M.
  • 10,481
  • 2
  • 9
  • 15
  • Nice, I definitely like `isspace()` and the `cycle` in there. That makes the code much easier to read as it explains the *what* the code is trying to accomplish. Of course, for a beginners class that may not be expected :) – Maarten Bodewes Oct 06 '19 at 13:19
  • Hi you seem to know what you're talking about! However could you put in some comments of whats happening in the code since i'm a beginner and have a hard time following what's happening, as mentioned in the comment above :) – Pro-Grammer Oct 06 '19 at 13:36
  • Thank you both for the feedback. I've updated my post with a bit more information. Let me know if I can explain anything else more clearly. – Paul M. Oct 06 '19 at 13:51
0

You can either ignore the encryption method for white space, or refactor your code to just run the encryption on actual words, eg by using plaintext.split(' ') to get a list of words to encrypt, then running encrypt/decrypt on each item in the list.

Here’s how to ignore whitespace during encryption. Note that this implementation assumes that by “skipping white space”, you mean you would normally still encrypt whitespace. The key still advances for whitespace, which is not wholly correct behaviour.

def encrypt(plain_text, key):
    ...
    for i in range(len(plaintextAsIntegers)):
        if plain_text[i] == ' ':
            encryptedtext += ' '
        else:
            encryptvalue = (plaintextAsIntegers[i] + keyAsIntegers[i % keyLength]) % 26 
            encyptedtext += chr(encryptvalue + 65)

Decrypt should just be the opposite procedure.

...
if encryptedtext[i] == ' ':
    plain_text += ' ':
else:
...

This makes the encryption weaker though, as it’s possible to guess which words might be which based on their lengths. Much better to include all white spaces (incl tabs etc) as a character to encrypt.

dijksterhuis
  • 1,225
  • 11
  • 25
  • I already voted up, but this question is incorrect, you need to keep a separate offset to indicate the next character in the key to use. Please fix. – Maarten Bodewes Oct 06 '19 at 13:08
  • @MaartenBodewes pretty sure that’s what they’re doing with this bit of code, no? `keyAsIntegers[i % keyLength]`... – dijksterhuis Oct 06 '19 at 13:14
  • The problem is that `i` advances even if a space is found. That's not how Vigenére is generally thought to operate. The ciphertext should be the same with or without spaces. At the very least this should be noted in the answer, but to be honest, I think the current answer would be considered wrong by most scholars. – Maarten Bodewes Oct 06 '19 at 13:17
  • Depends on your interpretation of “skip” tbh – dijksterhuis Oct 06 '19 at 13:19
  • @MaartenBodewes updated to include the assumption etc, thanks for pointing that out. – dijksterhuis Oct 06 '19 at 13:22