def _block_output(self, working_list): """Internal method that overrides the base class method. Formats the ciphertext in groups of 25 two-digit numbers on each line, if the user chooses. (May also call the base class method.) Called by the encrypt method. Named arguments: - working_list -- the list of numbers constituting the ciphertext. Returns: nothing. """ # Clear the screen. i_o.clear_screen() working_string = "" if i_o.yes_no("Would you like the output separated" + " into two-digit numbers?"): # If yes, check to see if the user wants line breaks. line_break = i_o.yes_no( "Would you like the encrypted text to be printed" + "in\n25-number lines for immproved readability?") # Build an output string of two-digit numbers. # If yes, insert a new line every 25 numbers. working_string = "\n" num = 1 while len(working_list) > 0: # Pop numbers off the list one at a time until empty. working_string += str(working_list.pop(0)) + " " if line_break: # Only use if the user wants line breaks. if num < 25: num += 1 else: working_string += "\n" num = 1 # end if # end if # end while self.ciphertext = working_string else: # If no, dump the entire list into ciphertext as one # string. self.ciphertext = "".join(str(n) for n in working_list) # Then call the original method. super()._block_output() return
def _get_keynumber(self, prompt, keylist=None, lbound=None, ubound=None): """Gets a keynumber for a cipher from the unser allows the user to abort. Arguments: - prompt -- prompt for the user. Named arguments: - keylist -- a list of valid entries (default None). - lbound -- lower bound for number, inclusive (default None). - ubound -- upper bound for number, inlcusive (default None). Returns: the keynumber, or None if the user aborts. """ keynumber = None got_keynumber = False # Loop until a valid key is obtained, or user aborts. while not got_keynumber: # Get a string response. string = i_o.get_string(prompt) # If the user didn't enter anything... if len(string) == 0: # Ask whether to abort. abort = i_o.yes_no( "You did not enter anything. Do you want to abort?") if abort: # If yes, drop out of the while loop with key = # None. break else: # Test the response for validity. got_keynumber = True # First, see if it's a number. try: keynumber = int(string) except ValueError: # Loop back if it's not. print("Sorry, that was not a number.") got_keynumber = False continue # end try # Check the number against the keylist (but only if # keylist exists). if keylist and (keynumber not in keylist): # Loop if there's a keylist and the number isn't in # it. print("Sorry, that is not a valid keynumber for this", "cipher.") got_keynumber = False # Check against upper and lower bounds, if they exist. elif lbound and (keynumber < lbound): print("Sorry, that number is too small.") got_keynumber = False elif ubound and (keynumber > ubound): print("Sorry, that number is too large.") got_keynumber = False # end if # end if # end while return keynumber
def _one_time_pad(self): """Internal method that implements a one-time pad at the user's option. Called by both encrypt and decrypt methods. Arguments: none. Returns: nothing. """ # Clear the screen. i_o.clear_screen() # Print summary info. print("Cipher: ", self.__str__()) print("Action: ", self.mode, "\n") # Print explanation of the one-time pad. if self.mode == "Encrypt": print( "One-Time Pad: In addition to encrypting your message\n", "using the " + self.__str__() + ", Secret Messages! can\n", "first encode your message using a one-time pad. This\n", "scrambles your message before it is encrypted. WARNING--\n", "if you use a one-time pad code, your message will be\n", "unrecoverable without entering the same code while\n", "decrypting. To be theoretically unbreakable, the one-time\n", "pad code must equal or exceed the length of your message,\n", "but a code of any length may be used.") else: print( "One-Time Pad: If this message was encrypted using a\n", "one-time pad code, you MUST enter the same code now in\n", "order to decrypt the message.") # If user chooses to use a one-time pad... if i_o.yes_no("Do you want to use a one-time pad on this cipher?"): done = False # Loop until a code is obtained, or user aborts. while done is False: # Get the one-time pad code from the user. pad_code = self._get_keyword("Enter a one-time code now: ") if pad_code == "": if i_o.yes_no( "Do you want to " + self.mode.lower() + "this " + "message without a one-time pad code?"): # If no one-time pad code, do nothing. return # end if (method exits) else: done = True # end if # end while # Work pad magic here. Simple modular addition/subtraction # is used. alphanum = ALPHABET + NUMBERS # Convert the pad code to a list of numbers. pad_code_list = [] for letter in pad_code: pad_code_list.append(alphanum.index(letter.upper())) # end for new_string = "" if self.mode == "Encrypt": mod = 1 else: mod = -1 # end if # This method operates on both letters and numbers. # Loop through the text, performing addition or subtraction # on each letter, using each number in the pad code in # succession. Note that if the one-time pad code is # shorter than the message, the code repeats. index = 0 for letter in self.plaintext: # Substitute recoded letter for original letter. new_string += ( alphanum[(alphanum.index(letter) + (pad_code_list[index] * mod)) % 36]) # Next number in code sequence; reset if at end. index = (index + 1) % len(pad_code_list) # end for # Put the results back into the plaintext attribute. self.plaintext = new_string # end if return
def _intelligent_encrypt(self): """Internal method that inserts flags for decryption. If the user opts for intelligent encryption, this method will insert special sequences for spaces, capital letters, basic punctuation (and itself), using bigrams (2-letter combinations) that rarely occur in English. Arguments: none. Returns: nothing. """ # Clear the screen first. i_o.clear_screen() # Print summary info. print("Cipher: ", self.__str__()) print("Action: ", self.mode, "\n") # Print explanation of intelligent encryption. print( "Intelligent Encryption/Decryption: Under ordinary\n", "circumstances, when a message is encrypted it is turned into\n", "a single string of upper-case text, with all spacing,\n", "punctuation and capitalization removed. Secret Messages!\n", "can encrypt your message so that flags are inserted to\n", "indicate spacing, punctuation and capitalization, which can\n", "then be restored upon decryption.\n") print( "Note that Intelligent Encryption/Decryption uses the\n", "following letter combinations, which do not occur in\n", "most Roman-alphabet-based languages: [FQ], [GX], [HX],\n", "[JQ], [JX], [PZ], [QG], [QK], [QY], [QZ], [WQ], [WZ], [XJ],\n", "[ZJ], [ZQ], [ZX]. If your messages contains abbreviations,\n", "code words, model numbers, map coordinates, etc., which may\n", "contain these letter combinations, you should NOT select\n", "Intelligent Encryption/Decryption.\n") # Get the user's choice. use_intel = i_o.yes_no("Use Intelligent Encryption?") if not use_intel: # If no, just exit. return else: # If yes, The first two characters of the decrypted text # will be "ZX", which will trigger _intelligent_decrypt when # it is called. space_sequences = ["FQ", "JX", "QK", "WZ", "ZJ"] new_text = "ZX" # Go through the message one character at a time. for char in self.plaintext: # Check for space. if char == " ": # Because spaces are so common, encoding them with a # single escape sequence could expose the sequence # to detection, thereby exposing the lengths of # individiual words. To counter this, the method # randomly selects one of five special sequences, # any of which can mark a space. new_text += space_sequences[random.randint(0, 4)] # First a series of tests for punctuation marks. Each # of these inserts a two-character sequence in place of # the character. elif char == ".": new_text += "HX" elif char == ",": new_text += "JQ" elif char == "?": new_text += "PZ" elif char == "!": new_text += "QG" elif char == "'": new_text += "QY" elif char == '"': new_text += "QZ" elif char == ":": new_text += "WQ" elif char == ";": new_text += "XJ" elif char == "-": new_text += "ZQ" # If it's not punctuation, check for a capital letter. elif char in ALPHABET: new_text += "GX" + char else: # If all else fails, it's just an ordinary letter/ # number. new_text += char.upper() # end if # end for # The message also ends with "ZX". Any characters added # by the encryption method are nulls and should be # discarded by _intelligent_decrpyt. new_text += "ZX" self.plaintext = new_text # end if return
def _get_keyword( self, prompt, keylist=ALPHABET, max_length=None, min_length=None): """Gets a keyword for a cipher from the user, allows the user to abort. The primary difference between _get_keyword and an ordinary input method like get_string is that _get_keyword ensures that no non-alphabetic characters are in the keyword (spaces are stripped) and that the keyword is returned in upper-case. Arguments: - prompt -- prompt for the user (allows keywords to be named). Named Arguments: - keylist -- allows the caller to pass a non-standard alphabet against which to check the user's input (default ALPHABET). - max_length -- allows the caller to control the maximum length of the keyword (default None). - min_length -- allows the caller to control the minimum length of the keyword (default None). Returns: the keyword, or an empty string if the user aborts. """ keyword = "" got_keyword = False # Loop until a valid keyword is obtained, or user aborts. while not got_keyword: # Get string response. keyword = i_o.get_string(prompt) # If the user didn't enter a keyword... if len(keyword) == 0: # Ask whether to abort. abort = i_o.yes_no( "You did not enter anything. Do you want to abort?") if abort: # If user aborts, drop out of the while loop with # keyword = "", which will be returned. break # end if # end if if min_length and (len(keyword) < min_length): # If too short, say so and loop back to beginning. print( "Sorry, your entry is too short.") continue # end if if max_length and (len(keyword) > max_length): # If too long, say so and loop back to beginning. print("Sorry, your entry is too long.") continue # end if # If it's the right length, check for invalid characters. got_keyword = True for letter in keyword: # Check each character to make sure it's a letter. if not (letter.upper() in keylist): print( "Sorry, your entry includes spaces or" + "other forbidden characters.") got_keyword = False # Break the for loop (not the while loop). break # end if # end for # end while return keyword.upper()
def _block_output(self): """Internal method that outputs encrypted text in five- character blocks, and can also insert newlines to keep long strings of blocks from running off the screen. if the user wishes. Called by the encrypt method. Arguments: none. Returns: nothing. """ # Clear the screen. i_o.clear_screen() # Ask whether to break the output into 5-character blocks. separate = i_o.yes_no( "Would you like the encrypted text to be printed in" + "five\n-character blocks for immproved readability?") line_break = i_o.yes_no( "Would you like the output to be broken into separate" + 'lines?\n(Warning: This will insert line breaks or") + '"hard returns"\ninto the output.)') new_text = "" if separate: index = 0 # Iterate over the ciphertext, five characters at a time. for pos in range(0, len(self.ciphertext), 5): # Add a five-character block, plus a space. new_text += self.ciphertext[pos:pos+5] + " " index += 1 if index == 10: index = 0 # end if # end for # Put result in ciphertext. self.ciphertext = new_text # end if # Ignore if the ciphertext is too short to break into multiple # lines. # Set line length. pos = 60 if line_break and (len(self.ciphertext) > pos): # Start with a new line. new_text = "\n" while pos < len(self.ciphertext): # Work backwards from the first character after the line # length, looking for a space. offset = 0 while ( (self.ciphertext[pos + offset] != " ") and (pos + offset != 0)): offset -= 1 # end while if offset == 0: # If offset landed on 0, the first character after # the line length is a space. In that case, take # the line, insert a newline character, and discard # the space. new_text += self.ciphertext[:pos] + "\n" self.ciphertext = self.ciphertext[pos+1:] elif pos + offset == 0: # If offset landed on the negative of pos, there # were no spaces within the line. Just take the # entire line. new_text += self.ciphertext[:pos] + "\n" self.ciphertext = self.ciphertext[pos:] else: # Otherwise, take the line up to and including the # space. new_text += self.ciphertext[:pos + offset + 1] + "\n" self.ciphertext = self.ciphertext[pos + offset + 1:] # end if # end while # When there is less than a full line left, take the entire # line. new_text += self.ciphertext # Put result back in self.ciphertext. self.ciphertext = new_text # end if return
def main(): """The main script function. Arguments: none. Returns: nothing. """ # Opening screen. i_o.welcome_screen() running = True # Runs until user quits. while running: # User chooses to encrypt or decrypt here. a_choice, action = i_o.input_from_menu( ["Encrypt", "Decrypt"], option_type="actions", allow_keystroke=True, keystroke_list=["E", "D"], confirm=True) # Runs only if user doesn't quit. if a_choice: action = action[0] print("You have chosen to " + action + ".\n") # User chooses a cipher here. c_choice, chosen_cipher = i_o.input_from_menu( IMPLEMENTED_CIPHERS, option_type="ciphers", allow_keystroke=True, keystroke_list=CIPHER_KEYSTROKES, confirm=True) # Runs only if user doesn't quit. if c_choice: chosen_cipher = chosen_cipher[0] print("You have selected " + chosen_cipher + ".\n") # User enters text here. text = i_o.get_string( "Please enter your text, or [ENTER] to go back:\n>> ") # Runs only if user enters something if len(text) > 0: # Create an object of the appropriate cipher class. # Then call the object's encrypt or decrypt method # with the user's text. cipher = CIPHER_CLASS[chosen_cipher](action, text) if action == "Encrypt": cipher.encrypt() output = cipher.ciphertext else: cipher.decrypt() output = cipher.plaintext # end if # If the method set nothing, the user aborted. if len(output) == 0: print("Process aborted.") # Else print the result. else: i_o.print_string(output, "Here is your result: ") # end if # Finished with the instance, so delete it. del cipher # end if # end if repeat = i_o.yes_no("Run again?") if not repeat: print("Thank you for using Secret Messages!") running = False # end if else: print("Thank you for using Secret Messages!") running = False