One-time pad encryption using letter shifting

This program asks the user to input a List of words. It then returns a list of encrypted words, and a second list which specifies the numerical shifts used to encrypt those words. The idea was obtained from https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/one-time-pad

import random

def rotate_letter(letter, number):
if letter == ' ':
return letter
if letter.islower():
start = ord('a')
elif not letter.islower():
start = ord('A')

normal_shift = ord(letter) - start
new_letter = start + (normal_shift + number) % 26
return chr(new_letter)

def avoid(letter):
avoid = ' ,!?¿,*+-%/@1234567890'
for i in avoid:
if i == letter:
return False
return True

def rotate_word(word):
new_word = ''
random_series = []
for letter in word:
if avoid(letter):
random_number = random.randint(1,26)
new_letter = rotate_letter(letter, random_number)
new_word += new_letter
random_series.append(random_number)
else:
new_word += letter
return (new_word, random_series)

def shift(words):
index = 0
encrypted_words = []
random_shifts = []
while index < len(words):
(new_word, random_series) = rotate_word(words[index])
encrypted_words.append(new_word)
random_shifts.append(random_series)
index += 1
return (encrypted_words, random_shifts)

def get_input():
words = []
while True:
user = (input('Input words to encrypt. Done when ready \n' ))
if user == 'Done' or user == 'done':
return words
words.append(user)

def start():
words = get_input()
encrypted_words = []
random_shifts = []
(encrypted_words, random_shifts) = shift(words)
print('UNENCRYPTED WORDS', words)
print('ENCRYPTED WORDS', encrypted_words)
print('RANDOM SHIFTS', random_shifts)

start()

• Are you using Python 2 or Python 3? Jan 6, 2017 at 21:00
• @Peilonrayz I would guess Python 3 because of his usage of input. Jan 6, 2017 at 22:10

Your function avoid is quite unnecessary and also confusing. I would expect avoid('@') to return True. When you store the letters as a global constant and as set, you should gain some speed (both because it does not have to be reconstructed again and again and because membership testing is very fast for a set. I replaced your function with letter not in letters_avoid.

Whenever you are doing string addition in Python, you are probably doing something wrong. This is because strings are immutable so for every addition a new string has to be created and the content of the strings to be added needs to be copied. This is quite slow. Rather, use a list to accumulate the letters of new_word and str.join them at the end.

When returning a tuple, the () are implicit. You can just write return a, b.

When iterating over words, you should just directly iterate over it, instead of its indices.

Using str.lower cuts down the double comparison if user == 'Done' or user == 'done' to if user.lower() == 'done':.

You should call your code with a if __name__ == '__main__': guard to allow importing your functions from another script without executing the whole script.

Final code:

import random

letters_avoid = set(' ,!?¿,*+-%/@1234567890')

def rotate_letter(letter, number):
if letter == ' ':
return letter
if letter.islower():
start = ord('a')
elif not letter.islower():
start = ord('A')

normal_shift = ord(letter) - start
new_letter = start + (normal_shift + number) % 26
return chr(new_letter)

def rotate_word(word):
new_word = []
random_series = []
for letter in word:
if letter not in letters_avoid:
random_number = random.randint(1, 26)
new_word.append(rotate_letter(letter, random_number))
random_series.append(random_number)
else:
new_word.append(letter)
return "".join(new_word), random_series

def shift(words):
encrypted_words = []
random_shifts = []
for word in words:
new_word, random_series = rotate_word(word)
encrypted_words.append(new_word)
random_shifts.append(random_series)
return encrypted_words, random_shifts

def get_input():
words = []
while True:
user = input('Input words to encrypt. Done when ready \n')
if user.lower() == 'done':
return words
words.append(user)

def start():
words = get_input()
encrypted_words, random_shifts = shift(words)
print('UNENCRYPTED WORDS', words)
print('ENCRYPTED WORDS', encrypted_words)
print('RANDOM SHIFTS', random_shifts)

if __name__ == "__main__":
start()