class BreakEncryption:
    #Constructor for BreakEncryption class
    def __init__(self, objCommon):
        try:
            self.common = objCommon
            self.encryption = Encryption(objCommon)
            self.keyGeneration = KeyGeneration(objCommon)
            self.decryption = Decryption(objCommon)
        except Exception as ex:
            print(
                "An error occurred while initializing class BreakEncryption. Error: ",
                ex)

    #String Representation
    def __str__(self):
        return "No member variables other than member objects to classes"

    #4. Break encryption and get original message using Baby Step Giant Step algorithm
    def BreakEncryptionGetMessage(self):
        try:
            print("\nIf your listening to somebody's conversation as a Man In The Middle",\
                  "and wish to break an encrypted message,")
            print(
                "you must have the knowledge of encrypted message and the header sent to the Reciever."
            )

            #Read Encrypted Message and Header from the file
            tupleEncMsgHead = self.encryption.ReadEncryptedMessageAndHeader()

            if len(tupleEncMsgHead) > 0:
                print(
                    "\nThe public key of the Receiver is required to break an encryption."
                )
                print(
                    "The Public Keys are usually publicly available via Key Hosting Services."
                )

                #Read public key from file
                receiversKeys = self.keyGeneration.ReadReceiversKeys(False)

                if len(receiversKeys) > 0:
                    encryptedMessage, header = tupleEncMsgHead[
                        0], tupleEncMsgHead[1]
                    primitiveElement = receiversKeys[0]
                    primitiveRaisedSecretModPrime = receiversKeys[1]
                    randomPrime = receiversKeys[2]

                    print(
                        "\nBreaking Encyption (using Baby Step Giant Step Algorithm)..."
                    )
                    self.__PerformBabyStepGiantStep(
                        encryptedMessage, header, primitiveElement,
                        primitiveRaisedSecretModPrime, randomPrime)

        except Exception as ex:
            print(
                "An error occurred in function BreakEncryption.BreakEncryptionGetMessage while processing. Error: ",
                ex)

    #To perform Baby-Step-Giant-Step algorithm using Receiver's public keys and Header to break Encryption
    #   and get the original message
    def __PerformBabyStepGiantStep(self, encryptedMessage, header,
                                   primitiveElement,
                                   primitiveRaisedSecretModPrime, randomPrime):
        try:
            randomVar = self.__ComputePowerVariable(header, primitiveElement,
                                                    randomPrime)
            #print("randomVar:", randomVar)
            if randomVar == -1:
                print(
                    "\nBaby Step Giant Step failed to break encryption for given Public Key and Header combination."
                )
                print(
                    "Please try again for a different Receiever\'s Public Key and Header combination..."
                )
            else:
                #Encrypted Message = message * (c^b mod p) mod p
                #message = Encrypted Message * Inverse of (c^b mod p) mod p

                #c^b mod p
                primitiveRaisedSecRaisedRandomModPrime = self.common.GetExponentiation(
                    primitiveRaisedSecretModPrime, randomVar, randomPrime)
                #print("primitiveRaisedSecRaisedRandomModPrime:", primitiveRaisedSecRaisedRandomModPrime)
                #message = Encrypted Message * Inverse of (c^b mod p) mod p
                message = (encryptedMessage *
                           self.decryption.GetInverseModPrime(
                               primitiveRaisedSecRaisedRandomModPrime,
                               randomPrime)) % randomPrime
                print("\nCracked Original Message:", message)

        except Exception as ex:
            print(
                "An error occurred in function BreakEncryption.__PerformBabyStepGiantStep while processing. Error: ",
                ex)

    #In the equation a = b ^ c mod n, below function computes 'c'
    # Representationally: answerVar = baseVar ^ powerVar Mod prime
    def __ComputePowerVariable(self, answerVar, baseVar, prime):
        try:
            dictLHS, dictRHS = {}, {}
            m = int(math.ceil(math.sqrt(prime - 1)))
            #print("m:", m)
            invBaseVar = self.decryption.GetInverseModPrime(baseVar, prime)
            #print("invBaseVar:", invBaseVar)
            invBaseVarRaisedm = self.common.GetExponentiation(
                invBaseVar, m, prime)
            #print("invBaseVarRaisedm:", invBaseVarRaisedm)

            #answerVar * (invBaseVar ^ m) ^ i  =  baseVar ^ j
            #where 0 <= i <=m
            #and 0 <= j < m

            #All L.H.S. values of equation answerVar * (invBaseVar ^ m) ^ i  =  baseVar ^ j
            for i in range(m + 1):
                valueLHS = (self.common.GetExponentiation(
                    invBaseVarRaisedm, i, prime) * answerVar) % prime
                dictLHS[i] = valueLHS

            #All R.H.S. values of equation answerVar * (invBaseVar ^ m) ^ powerVar  =  baseVar ^ otherPowerVar
            for j in range(m):
                valueRHS = self.common.GetExponentiation(baseVar, j, prime)
                dictRHS[j] = valueRHS

            listCombinationTuple = [(i, j) for i in dictLHS for j in dictRHS
                                    if dictLHS[i] == dictRHS[j]]
            #print("listCombinationTuple:", listCombinationTuple)
            if len(listCombinationTuple) > 0:
                powerVar = int((listCombinationTuple[0][0] * m) +
                               listCombinationTuple[0][1])
            else:
                #Baby-Step-Giant-Step algorithm failed to crack encryption
                powerVar = -1
            #print("powerVar:", powerVar)
            return powerVar
        except Exception as ex:
            print(
                "An error occurred in function BreakEncryption.__ComputePowerVariable while processing. Error: ",
                ex)
class Decryption:
    #Constructor for Decryption function
    def __init__(self, objCommon):
        try:
            self.common = objCommon
            self.keyGeneration = KeyGeneration(objCommon)
            self.encryption = Encryption(objCommon)
        except Exception as ex:
            print(
                "An error occurred while initializing class Decryption. Error: ",
                ex)

    #String Representation
    def __str__(self):
        return "common: " + str(self.common) + ", keyGeneration: " + str(
            self.keyGeneration) + ", encryption: " + str(self.encryption)

    #3. Decrypt an encrypted message using your (Receiver's) Private Key
    def DecryptMessage(self):
        try:
            #Read Encrypted Message and Header from file
            tupleEncMsgHead = self.encryption.ReadEncryptedMessageAndHeader()

            if len(tupleEncMsgHead) > 0:
                #Read keys from file
                receiversKeys = self.keyGeneration.ReadReceiversKeys(True)
                if len(receiversKeys) > 0:

                    encryptedMessage, header = tupleEncMsgHead[
                        0], tupleEncMsgHead[1]

                    #primitiveElement = receiversKeys[0]
                    #primitiveRaisedSecretModPrime = receiversKeys[1]
                    randomPrime = receiversKeys[2]
                    privateKey = receiversKeys[3]

                    #Calculating Header ^ PrivateKey Mod p
                    #where header = (g^b mod p)
                    #So, mathematically the formula can be written as (g^b mod p) ^a mod p
                    headerRaisedEncMsgModPrime = self.common.GetExponentiation(
                        header, privateKey, randomPrime)

                    #Calculating inverse of Header ^ PrivateKey Mod p
                    invHeaderRaisedEncMsgModPrime = self.GetInverseModPrime(
                        headerRaisedEncMsgModPrime, randomPrime)

                    decryptedMessage = (invHeaderRaisedEncMsgModPrime *
                                        encryptedMessage) % randomPrime

                    print("\nDecrypted Original Message:", decryptedMessage)

        except Exception as ex:
            print(
                "An error occurred in function Decryption.DecryptMessage while processing. Error: ",
                ex)

    #To perform extended euclidean to help find the inverse mod prime
    def PerformExtendedEuclidean(self, element, prime):
        try:
            if prime == 0:
                return [element, 1, 0]

            vals = self.PerformExtendedEuclidean(prime, element % prime)
            d = vals[0]
            a = vals[2]
            b = vals[1] - (element // prime) * vals[2]
            return [d, a, b]
        except Exception as ex:
            print(
                "An error occurred in function Decryption.PerformExtendedEuclidean while processing. Error: ",
                ex)

    #To calculate Inverse of an element mod prime
    def GetInverseModPrime(self, element, prime):
        try:
            vals = self.PerformExtendedEuclidean(element, prime)
            if vals[1] < 0:
                inverse = prime + vals[1]
            else:
                inverse = vals[1]
            return inverse
        except Exception as ex:
            print(
                "An error occurred in function Decryption.GetInverseModPrime while processing. Error: ",
                ex)