def GetDESDecryptionKey(self, encryption_key):
        temp = FeistelRoundInfo(0)
        temp.KeyLeft = self.ProcessPermutation(encryption_key, self.pc1BoxLeft)
        temp.KeyRight = self.ProcessPermutation(encryption_key, self.pc1BoxRight)

        # Run it through 16 Feistel rounds
        for i in range(self.nRounds):
            temp.RoundNumber = i
            self.RotateKeyHalves(temp, True)

        return temp
    def RunDESEncryption(self, input_bit_array, key_bit_array, output_file_handler, write_to_output_file = True):
        isEncryption = True
        roundInfo = FeistelRoundInfo(0)
        # Run the PC1 permutation on the key to select 56 bits of the 64 bits of the key
        roundInfo.KeyLeft = self.ProcessPermutation(key_bit_array, self.pc1BoxLeft)
        roundInfo.KeyRight = self.ProcessPermutation(key_bit_array, self.pc1BoxRight)

        # Run the Feistel rounds
        inputIndex = 0
        while inputIndex < len(input_bit_array):
            # Get the DES input of the appropriate length
            roundInfo.DataInput = self.GetDESBlock(input_bit_array, inputIndex)
            inputIndex += self.expectedBlockLength

            # Run the initial permutation on the data
            permutedInputBitArray = self.ProcessPermutation(roundInfo.DataInput, self.initialPermutationBox)
            roundInfo.DataInput = permutedInputBitArray

            # Run 16 Feistel rounds
            for i in range(self.nRounds):                      
                if i > 0:
                    roundInfo.DataInput = roundInfo.DataOutput
                roundInfo.RoundNumber = i
                roundInfo = self.ProcessFeistelRound(roundInfo.DataInput, roundInfo.KeyLeft, roundInfo.KeyRight, roundInfo.RoundNumber, isEncryption)

            # Run the final permutation of the data
            roundInfo.DataOutput = self.ProcessPermutation(roundInfo.DataOutput, self.finalPermutationBox)

            # Append to output file if appropriate
            if write_to_output_file == True:
                output_file_handler.write(roundInfo.DataOutput.ToBytes())
            lastOutputBlock = roundInfo.DataOutput

        return lastOutputBlock
    def ProcessReverseFeistelRound(self, input_block_bit_array, key_left_bit_array, key_right_bit_array, round_number):
        isEncryption = False

        # Create result FeistelRoundInfo
        roundResult = FeistelRoundInfo(0)
        roundResult.RoundNumber = round_number
        roundResult.DataInput = input_block_bit_array
        roundResult.DataOutput = MyBitArray()
        roundResult.KeyLeft = MyBitArray()
        roundResult.KeyLeft.extend(key_left_bit_array)
        roundResult.KeyRight = MyBitArray()
        roundResult.KeyRight.extend(key_right_bit_array)

        # Assign names to the halves of the data input for easy conceptualization
        # of reorganization
        originalMr = MyBitArray() # refers to original encryption Mr for the round
        newMr = MyBitArray()      # refers to the mangled original encryption Mr
                                  # XOR'd with the original encryption Ml
        self.SplitBitArray(roundResult.DataInput, [originalMr, newMr])

        # Generate the subkey for the round
        subkey = self.ProcessKeySchedule(roundResult, isEncryption)

        # Process Feistel Function
        mangledMr = self.FeistelFunction(originalMr, subkey)

        # Create the original Ml
        originalMl = newMr ^ mangledMr

        # Reconstruct the input of the parallel encryption round
        roundResult.DataOutput = MyBitArray()
        roundResult.DataOutput.FromBits(originalMl.bits)
        roundResult.DataOutput.extend(originalMr)

        return roundResult
    def ProcessFeistelRound(self, input_block_bit_array, key_left_bit_array, key_right_bit_array, round_number, is_encryption):
        # Requires a 64 bit input
        if len(input_block_bit_array) != 64 or len(key_left_bit_array) != 28 or len(key_right_bit_array) != 28:
            raise ValueError("ProcessFeistelRound received data or keys of an inappropriate length.")

        # Create result FeistelRoundInfo
        roundResult = FeistelRoundInfo(0)
        roundResult.RoundNumber = round_number
        roundResult.DataInput = input_block_bit_array
        roundResult.DataOutput = MyBitArray()
        roundResult.KeyLeft = MyBitArray()
        roundResult.KeyLeft.extend(key_left_bit_array)
        roundResult.KeyRight = MyBitArray()
        roundResult.KeyRight.extend(key_right_bit_array)

        # Split the data into two halves, call them Ml and Mr
        Ml = MyBitArray()
        Mr = MyBitArray()
        self.SplitBitArray(roundResult.DataInput, [Ml, Mr])

        # Generate the subkey for the round
        subkey = self.ProcessKeySchedule(roundResult, is_encryption)
        
        # Process Feistel Function
        mangledMr = self.FeistelFunction(Mr, subkey)
        
        # XOR with Ml
        newMr = mangledMr ^ Ml

        # Concatenate XOR result with original Mr
        roundResult.DataOutput.extend(Mr)
        roundResult.DataOutput.extend(newMr)
        
        return roundResult
    def RunDESDecryption(self, input_bit_array, key_bit_array, output_file_handler, write_to_output_file = True, last_input_block = False):
        roundInfo = FeistelRoundInfo(0)
        roundInfo.KeyLeft = MyBitArray()
        roundInfo.KeyRight = MyBitArray()
        self.SplitBitArray(key_bit_array, [roundInfo.KeyLeft, roundInfo.KeyRight])

        # Run the Feistel rounds
        inputIndex = 0
        while inputIndex < len(input_bit_array):
            # Get the DES input of the appropriate length
            roundInfo.DataInput = self.GetDESBlock(input_bit_array, inputIndex)
            inputIndex += self.expectedBlockLength

            # Run the reverse of the final permutation on the data
            permutedInputBitArray = self.ProcessPermutation(roundInfo.DataInput, self.finalPermutationBox, True)
            roundInfo.DataInput = permutedInputBitArray

            # Run 16 Feistel rounds
            for i in range(self.nRounds):
                # Set feistel round info correctly
                if i > 0:
                    roundInfo.DataInput = roundInfo.DataOutput
                roundInfo.RoundNumber = self.nRounds - i - 1

                roundInfo = self.ProcessReverseFeistelRound(roundInfo.DataInput, roundInfo.KeyLeft, roundInfo.KeyRight, roundInfo.RoundNumber)

            # Run the reverse of the initial permutation of the data
            roundInfo.DataOutput = self.ProcessPermutation(roundInfo.DataOutput, self.initialPermutationBox, True)

            # If decrypting and this is the last block of data to decrypt,
            # search for and remove padding bytes at the end of the message.
            if last_input_block == True or (len(input_bit_array) > self.expectedBlockLength and inputIndex >= len(input_bit_array)):
                roundInfo.DataOutput = self.RemovePadding(roundInfo.DataOutput)

            # Append to output file if appropriate
            if write_to_output_file == True:
                output_file_handler.write(roundInfo.DataOutput.ToBytes())
            lastOutputBlock = roundInfo.DataOutput

        return lastOutputBlock