#!/usr/bin/python """ File: Enigma.py This is a python implementation of the Enigma Cipher using an Object Oriented approach. Information source on the technical details: http://users.telenet.be/d.rijmenants/en/enigmatech.htm Note: I have refactored this a bit but it still needs more work especially in the area of the data class and the development of a user interface. Author: Chon Version: 2 Date: June 7 2012 """ from sys import exit from collections import deque 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 RotorData( object ): """ Contains the information used in the acutal Enigma Machine. """ # 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' # set lettercase to uppercase letters ('A' = 65) lettercase = 65 def __init__( self ): # convert Strings to deque objects and convert the strings to numbers self.rotor1 = Utilities.convertLettersToNumbers( deque( RotorData.rotor1 ) ) self.rotor2 = Utilities.convertLettersToNumbers( deque( RotorData.rotor2 ) ) self.rotor3 = Utilities.convertLettersToNumbers( deque( RotorData.rotor3 ) ) self.rotor4 = Utilities.convertLettersToNumbers( deque( RotorData.rotor4 ) ) self.rotor5 = Utilities.convertLettersToNumbers( deque( RotorData.rotor5 ) ) self.rotor6 = Utilities.convertLettersToNumbers( deque( RotorData.rotor6 ) ) self.rotor7 = Utilities.convertLettersToNumbers( deque( RotorData.rotor7 ) ) self.rotor8 = Utilities.convertLettersToNumbers( deque( RotorData.rotor8 ) ) self.Beta = Utilities.convertLettersToNumbers( deque( RotorData.Beta ) ) self.Gamma = Utilities.convertLettersToNumbers( deque( RotorData.Gamma ) ) self.reflA = Utilities.convertLettersToNumbers( deque( RotorData.reflectorA ) ) self.reflB = Utilities.convertLettersToNumbers( deque( RotorData.reflectorB ) ) # define an alphabet self.alphabet = Utilities.convertNumbersToLetters( range( 26 ) ) class Utilities( object ): """ Provides some general utilities used to manipulate lists/deque objects. """ @classmethod def convertLettersToNumbers( self, letters ): """ Converts a tuple of capital letters to numbers. """ numbers = deque() for letter in letters: numbers.append( int( ord( letter ) ) - RotorData.lettercase ) return numbers @classmethod def convertNumbersToLetters( self, numbers ): """ Converts a tuple of numbers to capital letters. """ letters = deque() for number in numbers: letters.append( chr( number + RotorData.lettercase ) ) return letters @classmethod def convertLetterToNumber( self, letter ): """ Converts a single uppercase letter to a number. E.g, 'A' --> 65 """ return int( ord ( letter.upper() ) ) - RotorData.lettercase @classmethod def convertNumberToLetter( self, number ): """ Converts a tuple of numbers to capital letters. """ return chr( number + RotorData.lettercase ) class Plugboard( RotorData ): """ A class defining the plugboard on the Enigma Machine. """ def __init__( self ): RotorData.__init__( self ) self.plugboard = range( 26 ) self.plugboardConnections = [] def connectTwoLetters( self, fromWhatLetter, toWhatLetter ): """ Connects a plug fromWhatLetter toWhatLetter. """ self.checkForValidInput( fromWhatLetter, toWhatLetter ) firstLetter = fromWhatLetter.upper() secondLetter = toWhatLetter.upper() fromWhatLetter = Utilities.convertLetterToNumber( fromWhatLetter.upper() ) toWhatLetter = Utilities.convertLetterToNumber( toWhatLetter.upper() ) if self.isPlugEmpty( fromWhatLetter, toWhatLetter ): self.plugboard[ fromWhatLetter ] = toWhatLetter self.plugboard[ toWhatLetter ] = fromWhatLetter self.createSteckerPair( firstLetter, secondLetter ) def isPlugEmpty( self, firstNumberToCheck, secondNumberToCheck ): """ Returns True if NumberToCheck is an empty plug otherwise False. """ indexOfFirstNumber = self.plugboard.index( firstNumberToCheck ) indexOfSecondNumber = self.plugboard.index( secondNumberToCheck ) return indexOfFirstNumber == firstNumberToCheck and \ indexOfSecondNumber == secondNumberToCheck def getPlugBoard( self ): """ Returns the plugboard. """ return self.plugboard def checkForValidInput( self, fromWhatLetter, toWhatLetter ): """ Checks input to ensure it's valid. """ if fromWhatLetter.upper() not in self.alphabet or toWhatLetter.upper() not in self.alphabet: raise invalidInput( "Letter must be between 'A' and 'Z'" ) def createSteckerPair( self, firstLetter, secondLetter ): """ Appends a tuple of firstLetter and secondLetter to self.plugboardConnections. """ steckerPair = ( firstLetter, secondLetter ) self.plugboardConnections.append( steckerPair ) return self.plugboardConnections class Rotor( RotorData ): """ Contains attributes and methods that relate to an Enigma rotor. """ def __init__( self, name, wheel ): RotorData.__init__( self ) self.rotorName = name self.rotorWheel = wheel self.rotorWheelInverse = self.invertRotorWheel() self.notchSettings = () self.outerRing = deque( range( 26 ) ) self.entryContact = deque( range( 26 ) ) self.ringSetting = 0 def encode( self, plainTextNum, invert = False ): """ Converts plaintext to ciphertext. Returns cipherText. -param plainTextNum is an ascii form of the plainText -param invert determines whether to encode using the inverse of the rotor. -return cipherText: scrambled representation of plainText (ascii) """ alphabet = range( 26 ) # determine which contact the signal arrives at entryContact = self.entryContact[ plainTextNum ] if invert == 'invert': exitContact = self.rotorWheelInverse[ plainTextNum ] else: # determine which contact the signal exits exitContact = self.rotorWheel[ plainTextNum ] # calculate the amount to shift the exit contact to determine # the exit position contactDifference = exitContact - entryContact cipherText = alphabet[ ( plainTextNum + contactDifference ) % 26 ] return cipherText 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 deque( iWheel ) 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.alphabet: raise invalidInput( "RingSetting must be between 'A' and 'Z'" ) setRingToThisNumber = Utilities.convertLetterToNumber( \ setRingToThisLetter.upper() ) self.ringSetting = setRingToThisNumber self.entryContact.rotate( setRingToThisNumber ) self.rotorWheel.rotate( setRingToThisNumber ) self.rotorWheelInverse.rotate( setRingToThisNumber ) def stepRotor( self, numSteps ): """ Steps the rotor numSteps. """ self.outerRing.rotate( numSteps ) self.entryContact.rotate( numSteps ) self.rotorWheelInverse.rotate( numSteps ) self.rotorWheel.rotate( numSteps ) 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. -param positions contains a list of the letter positions indicating where the rotors turn -returns a tuple containing the notchSettings """ # sets a default value if none given if not positions: positions = ( 'A', ) notches = () for letter in positions: if letter not in self.alphabet: raise invalidInput( "Notch position must be between 'A' and 'Z'" ) else: notches += ( Utilities.convertLetterToNumber( letter.upper() ), ) self.notchSettings = notches return self.notchSettings def atTurnOverPosition( self ): """ Returns True if ready to step rotor to the left. """ return self.outerRing[ 0 ] in self.notchSettings def setLetterInWindow( self, shiftToThisLetter = 'A' ): """ Sets the letter in the Enigma's window. """ if shiftToThisLetter.upper() not in self.alphabet: raise invalidInput( "Letter in Window must be between 'A' and 'Z'" ) shiftToThisNumber = Utilities.convertLetterToNumber( shiftToThisLetter.upper() ) # multiply by shiftDirection to make sure this is shifting the same # way as the rotor when stepped. shiftDirection = -1 shiftToThisNumber = shiftDirection * shiftToThisNumber % 26 self.outerRing.rotate( shiftToThisNumber ) self.entryContact.rotate( shiftToThisNumber ) self.rotorWheel.rotate( shiftToThisNumber ) self.rotorWheelInverse.rotate( shiftToThisNumber ) def __str__( self ): """ String representation. """ letter = self.convertNumberToLetter( self.outerRing[ 0 ] ) 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. """ # a +ive value shifts the rotors to the right, -ive to the left # direction of enigma was equivalent to a left shift 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 class EnigmaMachine( object ): """ Assembles all the components of the Enigma Machine and encyrpts the text. """ def __init__( self, reflector, plugboard, *rotors ): self.reflector = reflector self.plugboard = plugboard self.rotors = rotors self.numRotors = len( rotors ) self.shift = ShiftRotors( rotors ) self.rotorData = RotorData() 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.rotorData.alphabet: numbers += ( Utilities.convertLetterToNumber( letters ), ) return numbers def encrypt( self, text ): """ Encrypts a string of text and returns cipherText. """ cipherText = "" numbers = self.convertText( text ) print self.__str__() for num in numbers: # shift rotors self.shift.shiftRotors() 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 += Utilities.convertNumberToLetter( num ) return cipherText def machineSettings( self ): """ Provides the current machine settings. """ walzen = [] ringstellung = [] window = [] stecker = [] print "\nCurrent SetUp of the Machine:\n" # order of rotors & ring settings for index, rotor in enumerate( self.rotors ): rotorName = self.rotors[ index ].rotorName ringSetting = Utilities.convertNumberToLetter( \ self.rotors[ index ].ringSetting ) rotorSetting = Utilities.convertNumberToLetter( \ self.rotors[ index ].outerRing[ 0 ] ) walzen.append( rotorName ) ringstellung.append( ringSetting ) window.append( rotorSetting ) print " UKW: %s" % self.reflector.name 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.plugboardConnections: 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.alphabet[ rotors.outerRing[0] ] ) return '-'.join( map(str, reversed( rotorList ) ) ) class Printer( object ): """ Formats output of the cipherText. """ @classmethod def formatOutput( self, cipherText, interval = 0 ): """ Adds blanks to the ciphertext at intervals. Returns the string cipherText. """ interval = abs( interval ) if interval == 0: return cipherText else: return ' '.join( cipherText[ i:i+interval ] \ for i in range( 0, len( cipherText ), interval ) ) class User( RotorData ): """ Future user interface. """ def main( self, plainText ): """ The main program. """ rotorData = RotorData() # set the number of rotors rotor1 = Rotor( 'I', rotorData.rotor1 ) rotor2 = Rotor( 'II', rotorData.rotor2 ) rotor3 = Rotor( 'III', rotorData.rotor3 ) #rotor4 = Rotor( 'IV', rotorData.rotor4 ) # set the reflector reflector = Reflector( 'B', rotorData.reflB ) # initialize the plugboard plugboard = Plugboard() # Note: n-1 notches are important. The last rotor on the left will # not engage a rotor to its left since there is not a rotor there. # Enter the letter in the window for the notch position rotor1.setNotchPositions( 'Q', ) # Y=notch; Q=window rotor2.setNotchPositions( 'E', ) # M=notch; E=window rotor3.setNotchPositions( 'V', ) # D=notch; V=window #rotor4.setNotchPositions( 'J', ) # R=notch; J=window #rotor5.setNotchPositions( 'Z', ) # =notch; Z=window #rotor6.setNotchPositions( 'Z', 'A' ) # =notch; Z,A=window # valid values are between 'A' - 'Z' rotor1.setRingSetting( 'A' ) rotor2.setRingSetting( 'A' ) rotor3.setRingSetting( 'A' ) #rotor4.setRingSetting( 'A' ) # 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. plugboard.connectTwoLetters( 'a', 'b' ) plugboard.connectTwoLetters( 'x', 'o' ) plugboard.connectTwoLetters( 's', 'n' ) #plugboard.connectTwoLetters( 'q', 'd' ) #plugboard.connectTwoLetters( 'g', 'z' ) # Set Letter in Rotor Window # Valid input 'A' - 'Z' rotor1.setLetterInWindow( 'A' ) rotor2.setLetterInWindow( 'A' ) rotor3.setLetterInWindow( 'A' ) #rotor4.setLetterInWindow( 'A' ) # double step example: r1 = O, r2 = D, r3 = A """ Create the machine based on the setup above Note: - you can change the wheel order - the rotors in here are reversed so the last rotor in the parens is the left rotor on the enigma. """ machine = EnigmaMachine( reflector, plugboard, rotor3, rotor2, rotor1 ) # print machine settings machine.machineSettings() # encrypt plaintext to ciphertext cipherText = machine.encrypt( plainText ) # format text output intervalSpacing = 5 cipherText = Printer.formatOutput( cipherText, intervalSpacing ) print "\nThe plaintext to encrypt is:\n", plainText print "\nThe ciphertext is:\n", cipherText if __name__ == '__main__': user = User() plainText = "This is a test of the emergency broadcast system." exit( user.main( plainText ) )
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