#------------------------------------------------------------------------------- # Name: Enigma # Purpose: A representation of the Enigma Machine # # Author: chon # Source: http://users.telenet.be/d.rijmenants/en/enigmatech.htm # # Created: 17/09/2012 # Copyright: (c) chon 2012 # Licence: No licence #------------------------------------------------------------------------------- #!/usr/bin/env python from sys import exit class invalidInput( Exception ): """ Raises an error if the user inputs an invalid entry. """ def __init__( self, message ): self._message = message def __str__( self ): return str( self._message ) class Utilities( object ): """ Utility methods to manipulate lists. """ def __init__( self ): self._lettercase = 65 def convertLettersToNumbers( self, letters ): """ Converts a tuple of capital letters to numbers. """ return map( lambda x : ord( x ) - self._lettercase, string ) def convertNumbersToLetters( self, numbers ): """ Converts a tuple of numbers to capital letters. """ letters = [] for number in numbers: letters.append( chr( number + self._lettercase ) ) return letters def convertLetterToNumber( self, letter ): """ Converts a single uppercase letter to a number. E.g, 'A' --> 65 """ return int( ord ( letter.upper() ) ) - self._lettercase def convertNumberToLetter( self, number ): """ Converts a tuple of numbers to capital letters. """ return chr( number + self._lettercase ) class RotorData( object ): """ Contains the information used in the acutal Enigma Machine. """ def __init__( self ): # set rotor constants rotor1 = 'EKMFLGDQVZNTOWYHXUSPAIBRCJ' rotor2 = 'AJDKSIRUXBLHWTMCQGZNPYFVOE' rotor3 = 'BDFHJLCPRTXVZNYEIWGAKMUSQO' rotor4 = 'ESOVPZJAYQUIRHXLNFTGKDCMWB' rotor5 = 'VZBRGITYUPSDNHLXAWMJQOFECK' rotor6 = 'JPGVOUMFYQBENHZRDKASXLICTW' rotor7 = 'NZJHGRCXMYSWBOUFAIVLPEKQDT' rotor8 = 'FKQHTLXOCBJSPDZRAMEWNIUYGV' Beta = 'LEYJVCNIXWPBQMDRTAKZGFUHOS' Gamma = 'FSOKANUERHMBTIYCWLQPZXVGJD' # set reflector constants reflectorA = 'EJMZALYXVBWFCRQUONTSPIKHGD' reflectorB = 'YRUHQSLDPXNGOKMIEBFZCWVJAT' reflectorC = 'FVPJIAOYEDRZXWGCTKUQSBNMHL' self.rotor1 = self.convertStringToNumbers( rotor1 ) self.rotor2 = self.convertStringToNumbers( rotor2 ) self.rotor3 = self.convertStringToNumbers( rotor3 ) self.rotor4 = self.convertStringToNumbers( rotor4 ) self.rotor5 = self.convertStringToNumbers( rotor5 ) self.rotor6 = self.convertStringToNumbers( rotor6 ) self.rotor7 = self.convertStringToNumbers( rotor7 ) self.rotor8 = self.convertStringToNumbers( rotor8 ) self.Beta = self.convertStringToNumbers( Beta ) self.Gamma = self.convertStringToNumbers( Gamma ) self.reflectorA = self.convertStringToNumbers( reflectorA ) self.reflectorB = self.convertStringToNumbers( reflectorB ) self.reflectorC = self.convertStringToNumbers( reflectorC ) def convertStringToNumbers( self, string ): capitalA = 65 return map( lambda x : ord( x ) - capitalA, string ) class Plugboard( object ): """ An interface class for the plugboard. """ def __init__( self ): self._checkUserInput = CheckUserInput() self._connector = ConnectTwoLetters() def getPlugboard( self ): return self._connector.getPlugBoard() def connectTwoLetters( self, fromWhatLetter, toAnotherLetter ): self._checkUserInput.checkForValidInput( fromWhatLetter ) self._checkUserInput.checkForValidInput( toAnotherLetter ) self._connector.connectTwoLetters( fromWhatLetter, toAnotherLetter ) def getSteckerPairs( self ): return self._connector.getSteckerPairs() class CheckUserInput( Exception ): """ Checks the validity of the user's input. """ def __init__( self ): self._validInput = range( 26 ) self._text = Utilities() def checkForValidInput( self, userInput ): """ Checks input to ensure it's valid. """ input = self._text.convertLetterToNumber( userInput.upper() ) if input not in self._validInput: raise invalidInput( "Letter must be between 'A' and 'Z'" ) class ConnectTwoLetters( object ): """ A class that manages the connecting plugs in the plugboard. """ def __init__( self ): self._plugboard = range( 26 ) self._connections = [] self._text = Utilities() # A public method def connectTwoLetters( self, fromWhatLetter, toAnotherLetter ): """ Connects a plug fromWhatLetter toAnotherLetter. """ firstLetter = fromWhatLetter.upper() secondLetter = toAnotherLetter.upper() firstNumber = self._text.convertLetterToNumber( firstLetter ) secondNumber = self._text.convertLetterToNumber( secondLetter ) if self.isPlugEmpty( firstNumber ) and self.isPlugEmpty( secondNumber ): self._plugboard[ firstNumber ] = secondNumber self._plugboard[ secondNumber ] = firstNumber self.createSteckerPair( firstLetter, secondLetter ) def getPlugBoard( self ): """ Returns the plugboard. """ return self._plugboard def isPlugEmpty( self, aNumberToCheck ): """ Returns True if aNumberToCheck is an empty plug otherwise False. """ indexOfANumberToCheck = self._plugboard.index( aNumberToCheck ) return indexOfANumberToCheck == aNumberToCheck def createSteckerPair( self, firstLetter, secondLetter ): """ Appends a tuple of firstLetter and secondLetter to self._connections. """ steckerPair = ( firstLetter, secondLetter ) self._connections.append( steckerPair ) return self._connections def getSteckerPairs( self ): """ Returns the connections made on the steckerboard. """ return self._connections class Rotor( object ): """ An interface for the Rotor Class. """ def __init__( self, name, wheel ): self._notches = Notches() self._rotor = RotorFunctions( name, wheel ) def encode( self, plainTextNum, invert = False ): """ Returns the ciphertext from the plainTextNum. """ return self._rotor.encode( plainTextNum, invert ) def setRingSetting( self, setRingToThisLetter = 'A' ): """ Sets the ring settings on the Enigma's rotor. """ self._rotor.setRingSetting( setRingToThisLetter ) def getRingSetting( self ): """ Returns the ringsetting """ return self._rotor.getRingSetting() def stepRotor( self, numSteps ): """ Steps the rotor numSteps. """ self._rotor.stepRotor( numSteps ) def setNotchPositions( self, *positions ): """ Sets the notch location on a rotor. """ self._notches.setNotchPositions( *positions ) def getNotchSettings( self ): """ Returns the notch positions. """ return self._notches.getNotchSettings() def atTurnOverPosition( self ): """ Returns True if ready to step rotor to the left. """ notchSettings = self._notches.getNotchSettings() return self._rotor.atTurnOverPosition( notchSettings ) def setLetterInWindow( self, shiftToThisLetter = 'A' ): """ Sets the letter in the Enigma's window. """ self._rotor.setLetterInWindow( shiftToThisLetter ) def getLetterInWindow( self ): """ Returns the letter in the rotor's window """ return self._rotor.getLetterInWindow() def getName( self ): """ Returns the name of the rotor """ return self._rotor.getName() class Notches( object ): """ A class that represents the notch positions on the rotor. """ def __init__( self ): self._notchSettings = () self._capitalLetters = map( chr, range( 65, 91 ) ) self._text = Utilities() def setNotchPositions( self, *positions ): """ Sets the notch location on a rotor. This is the point where the rotor will turn the rotor to its left one notch. There can be more than one notch position on a rotor. """ message = "Notch position must be between 'A' and 'Z'" # sets a default value if none given if not positions: positions = ( 'A', ) for letter in positions: if letter not in self._capitalLetters: raise invalidInput( message ) else: notch = ( self._text.convertLetterToNumber( letter.upper() ), ) self._notchSettings += notch return self._notchSettings def getNotchSettings( self ): """ Returns the notchSettings """ return self._notchSettings class RotorFunctions(): """ Contains attributes and methods that relate to an Enigma rotor. """ def __init__( self, name, wheel ): self.rotorName = name self.rotorWheel = wheel self.iRotorWheel = self.invertRotorWheel() self.entryContact = range( 26 ) self.ringSetting = 0 self.capitalLetters = map( chr, range( 65, 91 ) ) self._text = Utilities() self._shift = 0 def encode( self, plainTextNum, invert = False ): """ Converts plaintext to ciphertext. Returns cipherText. """ numAlphabet = range( 26 ) # determine which contact the signal arrives at entryContact = self.entryContact[ ( plainTextNum + self._shift ) % 26 ] if invert == 'invert': exitContact = self.iRotorWheel[ ( plainTextNum + self._shift ) % 26 ] else: # determine which contact the signal exits exitContact = self.rotorWheel[ ( plainTextNum + self._shift ) % 26 ] # calculate the amount to shift the exit contact to determine # the exit position contactDifference = exitContact - entryContact cipherText = numAlphabet[ ( plainTextNum + contactDifference ) % 26 ] return cipherText def setRingSetting( self, setRingToThisLetter = 'A' ): """ Changes the position of the internal wiring relative to the rotor. Binds the outer ring and the inner ring together. """ if setRingToThisLetter not in self.capitalLetters: raise invalidInput( "RingSetting must be between 'A' and 'Z'" ) setRingToThisNumber = self._text.convertLetterToNumber( \ setRingToThisLetter.upper() ) self.ringSetting = setRingToThisNumber self._shift -= setRingToThisNumber def getRingSetting( self ): """ Returns the ringsetting """ return self.ringSetting def stepRotor( self, numSteps ): """ Steps the rotor numSteps. """ self._shift += numSteps def atTurnOverPosition( self, notchSettings ): """ Returns True if ready to step rotor to the left. """ return self.entryContact[ ( self._shift % 26 ) ] in notchSettings def setLetterInWindow( self, shiftToThisLetter = 'A' ): """ Sets the letter in the Enigma's window. """ if shiftToThisLetter.upper() not in self.capitalLetters: raise invalidInput( "Letter in Window must be between 'A' and 'Z'" ) shiftToThisNumber = self._text.convertLetterToNumber( shiftToThisLetter.upper() ) self._shift += shiftToThisNumber def getLetterInWindow( self ): """ Returns the letter in the rotor's window """ return self._text.convertNumberToLetter( self.entryContact[ ( self._shift % 26 ) ] ) def getName( self ): """ Returns the name of the rotor """ return self.rotorName def invertRotorWheel( self ): """ Returns the inverse of self.rotorWheel. """ wheel = list( self.rotorWheel ) iWheel = [ 0 ] * len( wheel ) for index, value in enumerate( wheel ): iWheel[ value ] = index return iWheel def __str__( self ): """ String representation. """ letter = self.convertNumberToLetter( self.entryContact[ ( self._shift % 26 ) ] ) return "The letter %s is in Rotor %s's window" % ( letter, self.rotorName ) class ShiftRotors( object ): """ Shifts the rotors on the Enigma Machine. """ def __init__( self, rotors ): self.rotors = rotors self.numRotors = len( rotors ) def shiftRotors( self ): """ Shifts rotors based on key strokes and notchSettings. """ numSteps = 1 for i in range( 0, self.howManyRotorsShift()+1 ): self.rotors[ i ].stepRotor( numSteps ) def doubleStepIsRequired( self, index ): """ Returns True if double step is needed, else False. """ return self.rotors[ index ].atTurnOverPosition() and index > 0 def doubleStep( self, index, stepDirectionAndAmount = 1 ): """ Double steps the appropriate rotors. """ self.rotors[ index ].stepRotor( stepDirectionAndAmount ) self.rotors[ index+1 ].stepRotor( stepDirectionAndAmount ) def howManyRotorsShift( self ): """ Returns an integer of the number of rotors that need to be shifted. A return value = 1 means there is one True in a row (from beginning of the list); 2 means there are 2 Trues in a row, and so on. -returns countNumberOfTrue - the number of Trues in a row """ odometerSetting = self.getOdometerSetting() countNumberOfTrue = 0 for index, value in enumerate( odometerSetting ): if odometerSetting[ index ] == True: countNumberOfTrue += 1 else: break return countNumberOfTrue def getOdometerSetting( self ): """ Returns a list of booleans indicating which Rotors are in their turn over positions. If a rotor is in its turn over position it contains a True otherwise False. """ odometerSetting = [ False ] * self.numRotors for i in range( self.numRotors ): odometerSetting[ i ] = self.rotors[ i ].atTurnOverPosition() # implements the double step of the rotors if required if self.doubleStepIsRequired( i ): self.doubleStep( i ) return odometerSetting class Reflector( object ): """ The Enigma's relfector. """ def __init__( self, name, reflector ): self._name = name self._reflector = reflector def getReflector( self ): """ Returns the reflector. """ return self._reflector def getName( self ): """ Returns the name of the rotor. """ return self._name class EnigmaMachine( object ): """ Assembles all the components of the Enigma Machine. """ def __init__( self, reflector, plugboard, *rotors ): self._reflector = reflector self._plugboard = plugboard self._rotors = rotors self._numRotors = len( rotors ) self._shift = ShiftRotors( rotors ) self._capitalLetters = map( chr, range( 65, 91 ) ) self._text = Utilities() def convertText( self, text ): """ Converts a string of text into a tuple of numbers. Returns numbers. """ numbers = () for letters in text.upper(): if letters in self._capitalLetters: numbers += ( self._text.convertLetterToNumber( letters ), ) return numbers def encrypt( self, text, verbose = True ): """ Encrypts a string of text and returns cipherText. """ cipherText = "" numbers = self.convertText( text ) if verbose: print self.__str__() for num in numbers: # shift rotors self._shift.shiftRotors() if verbose: print self.__str__() # passthrough plugboard num = self._plugboard.getPlugboard()[ num ] # passthrough rotors in order for rotor in range( self._numRotors ): num = self._rotors[ rotor ].encode( num ) # hit reflector num = self._reflector.getReflector()[ num ] # passthrough the inverse of the rotors for rotor in reversed( range( self._numRotors ) ): num = self._rotors[ rotor ].encode( num, 'invert' ) # passthrough plugboard num = self._plugboard.getPlugboard()[ num ] # create cipherText cipherText += self._text.convertNumberToLetter( num ) return cipherText def getMachineSettings( self ): """ Provides the current machine settings. """ walzen = [] ringstellung = [] window = [] stecker = [] print "\nCurrent Setup (Key) of the Machine:\n" # order of rotors & ring settings for index, rotor in enumerate( self._rotors ): rotorName = self._rotors[ index ].getName() ringSetting = self._text.convertNumberToLetter( \ self._rotors[ index ].getRingSetting() ) rotorSetting = self._rotors[ index ].getLetterInWindow() walzen.append( rotorName ) ringstellung.append( ringSetting ) window.append( rotorSetting ) print " UKW: %s" % self._reflector.getName() print " Walzen:", for rotor in reversed( walzen ): print "%s\t" % rotor, print "\nRingstellung:\t", for ring in reversed( ringstellung ): print "%s\t" % ring, print "\nRotor Window:\t", for letter in reversed( window ): print "%s\t" % letter, print "\n Stecker:", for pair in self._plugboard.getSteckerPairs(): print "%s%s " % ( pair[ 0 ], pair[ 1 ] ), print "\n" def __str__( self ): """ Displays letters in the rotors' window. """ rotorList = [] for rotors in self._rotors: rotorList.append( rotors.getLetterInWindow() ) return '-'.join( map(str, reversed( rotorList ) ) ) class Reader( object ): """ Abstract reader class. """ def __init__( self ): pass def read( self, fileName ): pass class KeyBoardReader( Reader ): """ Handles input from the keyboard. """ def read( self, fileName = "" ): """ Reads and returns text from the keyboard. """ return raw_input( "\nPlease enter a string to encrypt: " ) class FileReader( Reader ): """ Handles input from a file. """ def read( self, fileName ): """ Reads and returns text from a file. """ try: string = open( fileName, 'r' ).read() return string.rstrip( '\n' ) except IOError, e: print "\nFile Error." print "\nPlease ensure the file exists." class Printer( object ): """ An abstract print class. """ def __init__( self ): pass def write( self, text, interval, fileName ): pass class WriteToScreen( Printer ): """ Formats output of the cipherText to be displayed to standard output. """ def __init__( self ): pass def write( self, text, interval, fileName = "" ): """ Writes the output to the screen. """ interval = abs( interval ) if interval == 0: print text return text else: output = ' '.join( text[ i : i + interval ] \ for i in range( 0, len( text ), interval ) ) print output return output class WriteToFile( Printer ): """ Formats output of the cipherText to be displayed to standard output. """ def __init__( self ): pass def write( self, text, interval, fileName ): """ Writes the output to the screen. """ interval = abs( interval ) if interval == 0: with open( fileName, "w" ) as text_file: text_file.write( "%s" % text ) else: with open( fileName, "w" ) as text_file: text_file.write( "%s" % ' '.join( text[ i : i + interval ] \ for i in range( 0, len( text ), interval ) ) ) class User( object ): """ A basic user interface. """ def __init__( self ): _data = RotorData() # initialize components self._rotor1 = Rotor( 'I', _data.rotor1 ) self._rotor2 = Rotor( 'II', _data.rotor2 ) self._rotor3 = Rotor( 'III', _data.rotor3 ) self._reflector = Reflector( 'B', _data.reflectorB ) self._plugboard = Plugboard() self._readerDevice = KeyBoardReader() #self._readerDevice = FileReader() self._printerDevice = WriteToScreen() #self._printerDevice = WriteToFile() def setNotchSettings( self ): self._rotor1.setNotchPositions( 'Q', ) # Y=notch; Q=window self._rotor2.setNotchPositions( 'E', ) # M=notch; E=window self._rotor3.setNotchPositions( 'V', ) # D=notch; V=window def setRingSettings( self ): # valid values are between 'A' - 'Z' self._rotor1.setRingSetting( 'A' ) self._rotor2.setRingSetting( 'A' ) self._rotor3.setRingSetting( 'A' ) def setPlugboardConnections( self ): # Make Plugboard Connections # Each entry must be unique. Can't use any letter more than once # in the entire plugboard as only one plug will fit in each letter. If # a letter is repeated the connection is ignored. self._plugboard.connectTwoLetters( 'a', 'b' ) self._plugboard.connectTwoLetters( 'x', 'o' ) self._plugboard.connectTwoLetters( 's', 'n' ) self._plugboard.connectTwoLetters( 'r', 'd' ) self._plugboard.connectTwoLetters( 'g', 'z' ) def setLetterInRotorWindow( self ): # Set Letter in Rotor Window ( Valid input 'A' - 'Z' ) self._rotor1.setLetterInWindow( 'A' ) self._rotor2.setLetterInWindow( 'A' ) self._rotor3.setLetterInWindow( 'A' ) def printOutput( self, stringToWrite, interval = 0 ): """ Prints output to a device. """ self._printerDevice.write( stringToWrite, interval, "output.txt" ) def main( self ): """ The main program. """ # Set the Key on the Enigma Machine self.setNotchSettings() self.setRingSettings() self.setPlugboardConnections() self.setLetterInRotorWindow() # initialize the Enigma Machine machine = EnigmaMachine( self._reflector, self._plugboard,\ self._rotor3, self._rotor2, self._rotor1 ) # Get plainText to encrypt plainText = self._readerDevice.read( "testfile.txt" ) # print machine settings machine.getMachineSettings() # encrypt plaintext to ciphertext cipherText = machine.encrypt( plainText, False ) # print output intervalSpacing = 5 self.printOutput( cipherText, intervalSpacing ) if __name__ == '__main__': user = User() exit( user.main() )
Run
Reset
Share
Import
Link
Embed
Language▼
English
中文
Python Fiddle
Python Cloud IDE
Follow @python_fiddle
Browser Version Not Supported
Due to Python Fiddle's reliance on advanced JavaScript techniques, older browsers might have problems running it correctly. Please download the latest version of your favourite browser.
Chrome 10+
Firefox 4+
Safari 5+
IE 10+
Let me try anyway!
url:
Go
Python Snippet
Stackoverflow Question