Search code examples
pythonpython-3.xvigenere

Vigenere Cypher in Python


I'm trying to write a vigenere app in Python. Here is the way the app is supposed to run:

  1. run program
  2. enter "1" to encrypt or "2" to decrypt a message
  3. enter your cypher-key
  4. enter the input plaintext file you want to encrypt/decrypt
  5. enter the output file where the now-encrypted/decrypted text will be stored

The cypher key is a string of alpha-numeric chars that are then converted to an int and that number is used to rotate the plain-text to cypher-text. For example: A or a rotate one letter to the right to encrypt or one letter to the left to decrypt.

Almost everything works fine. If I enter a plain-text file in English, encrypt it and then decrypt that file it using the same key, the output file should look EXACTLY like the plain-text file with which I started... and it does except for the newlines. Some of the original newlines are gone and a few new ones are thrown in for good measure.

I've spent a LONG time trying to understand what I'm doing wrong. I will appreciate any help. Here's the entire code:

# ask user if he/she wishes to encrypt or decrypt a message
pmode = 0
while True:
    try:
        pmode = int(input("Enter 1 to encrypt or 2 to decrypt a message: "))
        while pmode not in {1, 2}:
            int(input("Oops! Please enter 1 or 2: "))
            break
        break
    except (ValueError, NameError):
        print("Oops! That entry is not supported, try again...")

# ask user for a cypher keyword
key = input("Enter codeword: ")

# create list to store converted key
keylength = len(key)
realkey = [] * keylength

# convert "key" from string of chars to string of ints
for i in key:
    if i.isalpha():
        if i.isupper():
            realkey.append(ord(i) % ord('A') + 1)
        elif i.islower():
            realkey.append(ord(i) % ord('a') + 1)
    elif ord(i) >= 48 and ord(i) <=57:
        realkey.append(int(i))
    else:
        realkey.append(0)

# prompt user for input file and open it
infile_name = input("Enter input file name: ")
infile = open(infile_name, 'r')

# prompt user for output file and open it
outfiler_name = input("Enter output file name: ")
outfile = open(outfiler_name, "w")

# if on encryption mode, encrypt
if pmode == 1:
    # iterate over inmessage and rotate (within ASCII chars 32 - 126) using realkey
    indx = -1
    # loop over infile, encrypt and write to outfile
    while True:
        # temp variable to store current char
        char = infile.read(1)
        indx += 1
        # stop when EOF is reached
        if not char:
            break
        else:
            if ord(char) + realkey[indx % keylength] > 126:
                outfile.write(chr((ord(char) - 126 + realkey[indx % keylength])))
            else:
                outfile.write(chr((ord(char) + realkey[indx % keylength])))
else:
    # iterate over inmessage and rotate (within ASCII chars 32 - 126) using realkey
    indx = -1
    # loop over infile, encrypt and write to outfile
    while True:
        # temp variable to store current char
        char = infile.read(1)
        indx += 1
        # stop when EOF is reached
        if not char:
            break
        else:
            if ord(char) + realkey[indx % keylength] < 32:
                outfile.write(chr((ord(char) + 126 - realkey[indx % keylength])))
            else:
                outfile.write(chr((ord(char) - realkey[indx % keylength])))

# close files
infile.close()
outfile.close()

Thanks.


Solution

  • because \n (a newline) is ascii 10 so remember ord('\n') == 10

    you have 2 possibilities at this point

    1. ord(char) + realkey[indx % keylength] < 32:
      • is usually true except when ord(key_letter)-ord('a') >= 22
      • if its true (for say 'a'(real_key value of 1) then 10+126+1 = 137 (or '\x89')
      • now when we try to decode it later again with same key_letter
        • ord('\x89') == 137
        • so our if statement of ord(char) + realkey[indx % keylength] < 32: is false
        • we will use our else rule to decode it 89 - 1 , well that definately does not get us back to 10, and so this has decoded the string wrong
    2. or its not , this case is even worse...

    I guess the answer is your algorithm will break under many many cases ... and should be entirely refactored .... I will give you some pointers on refactoring it that will help make it more testable for you

    • while testing hardcode your user key key="Yellow55"
    • likewise hardcode your string to encrypt and decrypt , make sure it has some harder characters (like newlines) my_string="Hello\nTest\t\rString\r\n" ... really it would be smart to just use string.printable as that should include all the ascii letters as a test case...
    • use functions... I cannot stress this enough!!!

    Functions

    def decrypt(string_to_decrypt,key):
        #do your stuff
    def encrypt(string_to_encrypt,key):
        #do your stuff
    
    • Instead of while True: i+=1 ... you should use for i,value in enumerate(my_list):

    • print things ... alot ... I mean really ... print out everything this will almost always make your error very obvious

      if you cannot print everything for somereason then at least use a debugger