Vigenere cipher corrupts some
Question: I am making a simple vigenere cipher encrypter/decrypter in python, and it
works for the most part. I'm not getting any errors, but some letters aren't
encrypted or decrypted (or both?) properly. Here is my code:

    
    
    import sys
    if not len(sys.argv) == 4:
        print "Not enough arguments."
        print "Usage: python vigener.py <encrypt/decrypt> 'ciphertext' 'key'"
        sys.exit()
    mode = sys.argv[1]
    ctext = sys.argv[2]
    key = sys.argv[3]
    if mode == "encrypt":
        print "Encrypting using vigener cipher..."
    elif mode == "decrypt":
        print "Decrypting using vigener cipher..."
    else:
        print "Unknown function '"+str(mode)+"'."
        print "Usage: python vigener.py <encrypt/decrypt> 'ciphertext' 'key'"
    MAGIC_NUMBER = 96
    ctext = ctext.lower()
    repeated_key = ( key * (1+len(ctext)/len(key)) )[:len(ctext)]
    if mode == "encrypt":
        ctext = ctext.replace(" ", "{")
        nums = [ord(ltr)-MAGIC_NUMBER for ltr in ctext]
        rk_nums = [ord(ltr)-MAGIC_NUMBER for ltr in repeated_key]
        enc_nums = [(num+rk_nums[ nums.index(num) ]) % 27 for num in nums]
        enc_ltrs = [chr(num+MAGIC_NUMBER) for num in enc_nums]
        print "".join(enc_ltrs)
    elif mode == "decrypt":
        enc_nums = [ord(ltr)-MAGIC_NUMBER for ltr in ctext]
        rk_nums = [ord(ltr)-MAGIC_NUMBER for ltr in repeated_key]
        dec_nums = [(num-rk_nums[ enc_nums.index(num) ]) for num in enc_nums]
        dec_nums2 = [ (num + 27 if num < 1 else num) for num in dec_nums]
        dec_ltrs = [chr(num+MAGIC_NUMBER) for num in dec_nums2]
        dec_str = "".join(dec_ltrs)
        dec_str = dec_str.replace("{", " ")
        print "".join(dec_str)
    

and here is my terminal output:

    
    
    $ python vigener.py encrypt 'this is confidential' 'secretkey'
    Encrypting using vigener cipher...
    lmljeljeagsiliyslltq
    $ python vigener.py decrypt 'lmljeljeagsiliyslltq' 'secretkey'
    Decrypting using vigener cipher...
    thts ts conftfenttal
    $
    

It seems to only encrypt/decrypt _some_ letters incorrectly. What the heck is
going on?


Answer: Short answer - these two lines need to be fixed:

    
    
    enc_nums = [(x + y) % 27 for (x, y) in zip(nums, rk_nums)]
    ...
    dec_nums = [(x - y) % 27 for (x, y) in zip(enc_nums, rk_nums)]
    

* * *

Long answer: The way your code uses `list.index()` is a logical error.

Suppose we're doing encryption, and the plaintext is `'banana'`, key is
`'secretkey'`. In the old code, this is what happens:

    
    
    nums = [2, 1, 14, 1, 14, 1]      # 'banana'
    rk_nums = [19, 5, 3, 18, 5, 20]  # 'secret'
    enc_nums = [
      ( 2 + rk_nums[nums.index( 2)]) % 27,
      ( 1 + rk_nums[nums.index( 1)]) % 27,
      (14 + rk_nums[nums.index(14)]) % 27,
      ( 1 + rk_nums[nums.index( 1)]) % 27,
      (14 + rk_nums[nums.index(14)]) % 27,
      ( 1 + rk_nums[nums.index( 1)]) % 27 ]
    

We can elaborate on what happens with the list `enc_nums`:

    
    
    enc_nums = [
      ( 2 + rk_nums[0]) % 27,
      ( 1 + rk_nums[1]) % 27,
      (14 + rk_nums[2]) % 27,
      ( 1 + rk_nums[1]) % 27,
      (14 + rk_nums[2]) % 27,
      ( 1 + rk_nums[1]) % 27 ]
    

The problem occurs when a letter occurs in the plaintext more than once. The
method `list.index()` returns the index of the first occurrence. Thus the
wrong index of the key (`rk_nums`) is used for encrypting the letters.

The easy solution is to use the
[`zip()`](https://docs.python.org/2/library/functions.html#zip) function,
which pairs up elements from both lists at the same index. For example,
`zip([9, 8, 7, 6], [0, 1, 2, 3])` will return the list `[(9,0), (8,1), (7,2),
(6,3)]`. This way, you can ensure that the plaintext numbers and key numbers
are used in sync all the time.

