1

I have constructed a Vigenere cipher function that works reasonably well, except for the fact that it does not pass the coding test that I'm required to run it through.

This is due to the fact that I'm using the ordinal values, while the test is expecting me to use a function to just rotate through a string letters, I think.

I have already run through the issue of dealing with non-alpha characters and got it to work with both upper case and lower case characters. However, it seems to fall apart if the key has any variance of upper case or lower case characters i.e. the key is lower case but the plain text is upper.

def encrypt(text, key):
    cipher_text = []
    key = list(key) 
    if len(text) == len(key): 
        return(key) 
    else: 
        for i in range(len(text) - 
                       len(key)): 
            key.append(key[i % len(key)]) 
    for i in range(len(text)): 
        a = text[i]
        if a.isalpha() and a.islower():
            x = ((ord(text[i]) + ord(key[i])-97) % 26 + 97)
            cipher_text.append(chr(x))
        elif a.isalpha() and a.isupper():
            x = ((ord(text[i]) + ord(key[i])-65) % 26 + 65)
            cipher_text.append(chr(x))
        else: 
            cipher_text.append(a) 

    return("" . join(cipher_text)) 


def main():
    mess = input(str("What is message?"))
    key = input("What is key?")
    print(encrypt(mess, key))

if __name__ == '__main__':
    main()

For vigenere.encrypt('BaRFoo', 'BaZ')

    You should have returned this:         
        'CaQGon'                           

    But you actually returned this:        
        'PtDTha'
  • I get different answers if I use all upper case and all lower case. That seems to indicate that you do not have it working in both upper and lower case. – glibdud Aug 23 '19 at 19:56
  • So many errors in this short program.. 1. if key length equals source length it does nothing and returns the key instead. 2. you substract 65 or 97 from key[i] depending on whether text[i] is lower or upper. What about making it depend on key[i]? 3. you don't substract 65 / 97 from text[i], but you should. 4. return is not a function. No parentheses required. – Adrian W Aug 23 '19 at 20:29

2 Answers2

0

from your single example it seems like the key is case insensitive:

def encrypt_letter(c, k):
  if c.isupper():
    base = ord('A')
  else:
    base = ord('a')

  return chr((ord(c) - base + ord(k) - ord('a')) % 26 + base)

def encrypt(text, key):
    key = key.lower()

    return "".join([encrypt_letter(text[i], key[i % len(key)]) for i in range(len(text))])

def main():
    print(encrypt('BaRFoo', 'BaZ'))

if __name__ == '__main__':
    main()
Kfir Dadosh
  • 1,411
  • 9
  • 9
0

First of all, I don't program in python, so excuse my form. I however tested everything in online compiler.

Before I answer your question, I'm not sure about this segment:

    if len(text) == len(key): 
        return(key) 
    else: 
        for i in range(len(text) - 
                       len(key)): 
            key.append(key[i % len(key)]) 

I read that as "If the key has the same length as plaintext, the key is the ciphertext", which obviously isn't true, unless the key is "aaaaaaaaa...". I would expect something like this:

    if len(text) > len(key): 
        for i in range(len(text) - 
                       len(key)): 
            key.append(key[i % len(key)])
#   else:
#       I don't care - the key is long enough

From Kfir Dadosh's answer I'd also point out, that you don't actually need this step and might as well directly access the key as key[i % len(key)].

As for the issue you were referring to. You only check whether plaintext (message) is lowercase or uppercase and change the key (let's call it normalization - the act of converting letter to number in the range 0-25 denoting it's position in alphabet) according to that.

        if a.isalpha() and a.islower():
            x = ((ord(text[i]) + ord(key[i])-97) % 26 + 97)
Here you take raw      ^                ^
ascii value instead of |                |
normalized (0-25) number                |
                                        |
Here you normalize the key according to the
message case

Following this are some heavy spoilers, so if you understand and want to solve the problem yourself, then stop reading here.


I'd suggest to separate the normalization and encryption steps, to avoid the confusion. Let's get rid of the special characters first instead of last, because it's easy to do and we'll only have to worry about letters:

        if not (text[i].isalpha()):
            cipher_text.append(text[i]) 
            continue; # done with this symbol - skip to the next one

Then normalize the letters, with the help of built in method. Let's use variables p for plaintext, k for key and c for ciphertext (later):

        p = ord(text[i].lower()) - ord('a') # converts to lowercase, then to number
        k = ord(key[i].lower()) - ord('a')  # both key AND plaintext

I used ord('a') instead of 65 as I'm used to that and find it clearer, but it is a matter of preference and language traditions (which I'm not accustomed with).

Then the encryption step:

        c = (p + k) % 26;

And now to restore the capitalization. We destroyed it in the variables p and k, but we still have their source array text[] with intact values. We can use that to restore the capitalization (assuming the case should be inherited from plaintext):

        if (text[i].islower()):
            cipher_text.append(chr(c + ord('a')))
        elif (text[i].isupper()):
            cipher_text.append(chr(c + ord('A')))

That completes the main loop. Feel free to modify it to be more python compliant.

    for i in range(len(text)):
        if not (text[i].isalpha()):
            cipher_text.append(text[i]) 
            continue;

        p = ord(text[i].lower()) - ord('a')
        k = ord(key[i].lower()) - ord('a')
        # k = ord(key[i % len(key)].lower()) - ord('a')
        # if you skipped the key lengthening process

        c = (p + k) % 26;

        if (text[i].islower()):
            cipher_text.append(chr(c + ord('a')))
        elif (text[i].isupper()):
            cipher_text.append(chr(c + ord('A')))
        else:
            # not sure if this can happen in python, but if it does, we're probably
            # in trouble
            # otherwise it might as well be merged with the elif above

    return("" . join(cipher_text)) 
veprolet
  • 361
  • 2
  • 12
  • between veprolet and @Kfir Dadosh i think the answer is here. Kfir Dadosh the issue is special characters and veprolet I think the python context is just slightly off. I'll keep playing with it and let you know what comes up. – Chip Woodson Aug 24 '19 at 19:01
  • @ChipWoodson Note, that Kfir's answer restructured the code well into functions, which I avoided only to keep more resemblance to your code and since I felt the function is short enough. He also skips the key lengthening part and accesses the key directly as `key[i % len(key)]`, which saves heaps of memory, although again your approach would work too. I edited that into my answer. Feel free to further edit my answer to comply with python context, because as I said, I know nothing about that. – veprolet Aug 25 '19 at 12:38