class EnigmaMachine(Encoder):

    def __init__(self, *configs):
        self.plugboard = Plugboard(configs[-1])

        self.rotors = []  # initially no rotors
        for rotor_path in configs[:-1]:
            self.rotors.append(Rotor(rotor_path))

        self.reflector = Reflector(13)

    def encode(self, input):
        input = ord(input) - ord('A')  # A -> 0, ..., Z -> 25
        output = self.plugboard.encode(input)
        output = self.pass_rotors('encode', output)
        output = self.reflector.encode(output)
        output = self.pass_rotors('decode', output)
        output = self.plugboard.decode(output)

        if len(self.rotors) > 0:  # check for rotors
            self.rotate_rotors(0)  # rotate necessary rotors
        return chr(output + ord('A'))

    def encode_message(self, message):
        encoded_message = ''
        for letter in message:
            if letter == ' ':
                encoded_message += ' '
            else:
                encoded_message += self.encode(letter)
        return encoded_message

    def decode(self, output):
        return self.encode(output)  # reciprocal cipher

    def rotate_rotors(self, rotor_i):
        if self.rotors[rotor_i].rotate():
            rotor_i += 1
            if rotor_i < len(self.rotors):
                self.rotate_rotors(rotor_i)

    def pass_rotors(self, transformer, input):
        direc = -1 if transformer == 'decode' else 1
        for rotor_num, rotor in enumerate(self.rotors[::direc]):
            # get output of current rotor
            if transformer == 'encode':
                input = rotor.encode(input)
            else:  # decode
                input = rotor.decode(input) 
        return input
Beispiel #2
0
class Enigma:
    """
        This class simulates the Enigma machine

        As models M3 and M4 of the Enigma machine, it supports 3 or 4 rotors configuration.

        The order of reflector and rotors definition respect the physical order (left-to-right).

        :param reflector: The reflector associated to the Enigma machine
        :type reflector: Reflector
        :param *rotors:  The rotors associated to the Enigma machine
        :type *rotors: Variable length rotor list
        :raises: :class:`TypeError, ValueError`: If one of the provided parameter is invalid

        Example::
            enigma = Enigma(ReflectorB(), RotorI(), RotorII(), RotorIII("V"))
            enigma.encode("HELLOWORLDHELLOWORLDHELLOWORLD")

            enigma = Enigma(ReflectorA(), RotorIV("E", 18), RotorV("Z", 24), RotorBeta("G", 3), RotorI("P", 5))
            enigma.plugboard.set("PC XZ FM QA ST NB HY OR EV IU")
            enigma.encode("BUPXWJCDPFASXBDHLBBIBSRNWCSZXQOLBNXYAXVHOGCUUIBCVMPUZYUUKHI")
    """
    def __init__(self, reflector, *rotors):
        self._rotor_chain = RotorChain(reflector, *rotors)
        self.plugboard = Plugboard()
        self.__alpha_string_validator = Validator(
            TypeValidator(str), LengthValidator(1, lambda x, y: x >= y),
            AlphaValidator())

    def encode(self, string):
        """
        Perform the whole encoding of a string on the Enigma machine

        Each character of the string is first encoded by the plug board then the character is encoded through the
        rotor chain and finally the character is encoded by the plug board again.

        :param char: The string to encode
        :type char: str
        :return: The encoded string
        :rtype: str
        """
        self.__alpha_string_validator.validate(string)
        encoded_string = ""
        for letter in string:
            encoded_string += self.plugboard.encode(
                self._rotor_chain.encode(self.plugboard.encode(letter)))
        return encoded_string

    def reset(self):
        """
        Reset all rotors of the machine to position to "A" and the ring setting to 1
        """
        self._rotor_chain.reset()

    @property
    def plugboard(self):
        """
        The plug board associated to the Enigma machine

        :getter: Returns the plug board
        :setter: Sets the plug board
        :type: Plugboard
        """
        return self.__plugboard

    @plugboard.setter
    def plugboard(self, plugboard):
        self.__plugboard = plugboard

    @property
    def reflector(self):
        """
        The reflector associated to the Enigma machine

        :getter: Returns the reflector
        :setter: Sets the reflector
        :type: Reflector
        """
        return self._rotor_chain.reflector

    @reflector.setter
    def reflector(self, reflector):
        self._rotor_chain.reflector = reflector

    @property
    def rotors(self):
        """
        The rotors associated to the Enigma machine

        :getter: Returns the list of rotors
        :setter: Sets the list of rotors
        :type: Rotors list
        """
        return self._rotor_chain.rotors

    @rotors.setter
    def rotors(self, rotors):
        self._rotor_chain.rotors = rotors
Beispiel #3
0
class Enigma:
    """
    The core class for the Enigma machine. Takes in a settings dictionary as provided by the user, and constructs the
    required machine from the settings blueprints. The structure of the machine is as follows:
    1. Plugboard with required plugleads
    2. Housing - a simple list from A-Z, never rotates
    3. Rotors - 3 or 4 with respective pins and contacts, these rotate with each 'key press'
    4. 1 Reflector - which will map the final rotor output to a different letter before passing the signal rightwards.
    """
    def __init__(self, settings: dict):
        self.settings = settings
        self.root = None
        self.rotor_box = {
            "Housing": {
                'contacts': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                'notch': None
            },
            "Beta": {
                'contacts': 'LEYJVCNIXWPBQMDRTAKZGFUHOS',
                'notch': None
            },
            "Gamma": {
                'contacts': 'FSOKANUERHMBTIYCWLQPZXVGJD',
                'notch': None
            },
            "I": {
                'contacts': 'EKMFLGDQVZNTOWYHXUSPAIBRCJ',
                'notch': 'Q'
            },
            "II": {
                'contacts': 'AJDKSIRUXBLHWTMCQGZNPYFVOE',
                'notch': 'E'
            },
            "III": {
                'contacts': 'BDFHJLCPRTXVZNYEIWGAKMUSQO',
                'notch': 'V'
            },
            "IV": {
                'contacts': 'ESOVPZJAYQUIRHXLNFTGKDCMWB',
                'notch': 'J'
            },
            "V": {
                'contacts': 'VZBRGITYUPSDNHLXAWMJQOFECK',
                'notch': 'Z'
            },
            "A": {
                'contacts': 'EJMZALYXVBWFCRQUONTSPIKHGD',
                'notch': None
            },
            "B": {
                'contacts': 'YRUHQSLDPXNGOKMIEBFZCWVJAT',
                'notch': None
            },
            "C": {
                'contacts': 'FVPJIAOYEDRZXWGCTKUQSBNMHL',
                'notch': None
            },
        }

    def create_machinery(self):
        """
        Adds in turn each part of the machine as specified in the user settings.
        """

        # Add plugboard with respective pluglead pairs
        self.board = Plugboard(self.settings)
        # Add housing
        self.add("Housing")
        # Add rotors. Rotors are 'inserted' in reverse order from right to left
        for i in range(len(self.settings['rotors'].split(' ')) - 1, -1, -1):
            rotor_name = self.settings['rotors'].split(' ')[i]
            ring_setting = self.settings['ring_settings'].split(' ')[i]
            initial_position = self.settings['initial_positions'].split(' ')[i]
            self.add(rotor_name, ring_setting, initial_position)

        # Add reflector
        self.add(self.settings['reflector'])

    @abstractmethod
    def add(self,
            name: str,
            ring_setting: int = 1,
            initial_position: str = 'A'):
        """
        Adds a named mechanical part to the machine from right to left, and then adjusts the default part setting
        to reflect the desired ring setting and initial position

        :param name: Name of the part being added (see self.rotor_box in the __init__ method for details)
        :param ring_setting: An integer from 1-26 determining the pin-to-contact mapping for the rotor
        :param initial_position: The starting position for the rotor
        """
        if self.root is None:
            self.root = Rotor(name, self.rotor_box, ring_setting,
                              initial_position)
            self.root.adjust_ring_setting()
            self.root.adjust_starting_positions()
        else:
            ptr = self.root
            while True:
                if ptr.left is None:
                    ptr.left = Rotor(name,
                                     self.rotor_box,
                                     ring_setting,
                                     initial_position,
                                     right=ptr)
                    ptr.left.adjust_ring_setting()
                    ptr.left.adjust_starting_positions()
                    break
                else:
                    ptr = ptr.left

    def encode(self, message: str) -> str:
        """
        Takes the users input message and runs each character through the enigma machine from right to left, and back
        again, to produce an encoded character. Encoded characters are combined into a single list and returned/printed
        as one encoded/decoded message.

        :param message: The message to be decoded/encoded
        :return: decoded/encoded message string
        """
        i = 0
        encoded_phrase = []
        # For each character in the message
        while i < len(message):
            character = message[i]
            # Run the character through the plugboard
            character = self.board.encode(character)

            ptr = self.root
            # Rotate all rotors as necessary
            ptr.left.key_press()

            # Assign the Housing (first "ptr.contacts") index of the character to input_index inside the first rotor object
            ptr.left.input_index = ptr.contacts.index(character.upper())
            ptr = ptr.left

            while True:
                # If there is another rotor to the left
                if ptr.left is not None:
                    # If Housing is to the right of the current rotor...
                    if ptr.right.name == 'Housing':
                        # ..pass the Housing/input index to the first rotor pins, to get the associated contact/output index
                        ptr.rotor_encode_left(ptr.input_index)
                    else:
                        # Else if it is a rotor to the right, grab that rotors encoded/output character
                        ptr.rotor_encode_left(ptr.right.output_index)
                    # Set pointer to the next part to the left and repeat
                    ptr = ptr.left
                else:
                    # No more rotors, now run signal through the reflector
                    ptr.rotor_encode_left(ptr.right.output_index)
                    break

            # Pass signal from reflector back through to the plugboard
            ptr = ptr.right
            while True:
                if ptr.right:
                    # If there is another machine element to the right position, encode as normal
                    ptr.rotor_encode_right(ptr.left.output_index)
                    ptr = ptr.right
                else:
                    # If there are no more parts to pass the signal through, encode, run through the plugboard if it
                    # exists, then append the output to the output phrase list
                    ptr.rotor_encode_right(ptr.left.output_index)
                    if self.board:
                        ptr.output_char = self.board.encode(ptr.output_char)
                    encoded_phrase.append(ptr.output_char)
                    break
            # Increase i by one to then loop back and start again for the next character
            i += 1

        print(f"Input Phrase: {message}")
        print(f"Encoded Phrase: {''.join(encoded_phrase)}")

        if len(self.settings['rotors'].split(' ')) == 3:
            print(f'Final Rotor Positions:{self.root.left.left.left.position}'
                  f'{self.root.left.left.position}'
                  f'{self.root.left.position}')
        else:
            print(
                f'Final Rotor Positions:{self.root.left.left.left.left.position}'
                f'{self.root.left.left.left.position}'
                f'{self.root.left.left.position}'
                f'{self.root.left.position}')

        return ''.join(encoded_phrase)