def SingleByteXORCipher(cipher):


    #cipher = bytes.fromhex(
    #  '1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736')

    potential_messages = []
    for key_value in range(256):
        message = MiscFunctions.singleCharacterXOR(cipher, key_value)
        score = MiscFunctions.getEnglishScore(message)
        data = {
            'message': message,
            'score': score,
            'key': key_value
        }
        potential_messages.append(data)
    return sorted(potential_messages,
            key=lambda x: x['score'], reverse=True)[0]
def BreakRepeatingKeyXOR(ciphertext):
    """ Attempts to break repeating-key XOR encryption """

    averages_distances = []

    #Take the keysize from the suggested range

    for keysize in range(1,41):
        
        #Initialize list to store Hamming Distances for this keysize
        distances = []
        

        #Break the ciphertext into chunks the lenght of the keysize

        chunks = [ciphertext[i: i +keysize] for i in range(0,len(ciphertext),keysize)]

        while True:
            try:
                #Take the two chunks at the beginning of the list
                #Get the Hamming Distance between these 2 chunks

                chunk_1 = chunks[0]
                chunk_2 = chunks[1]

                distance = MiscFunctions.calculateHammingDistance(chunk_1,chunk_2)

                #Normalize the result by dividing by the keysize

                distances.append(distance/keysize)

                #Remove these 2 chunks so when the loop starts over we get the next 2 parts of the cipher

                del chunks[0]
                del chunks[1]

                #When an exception Occurs (indicating all chunks have been processed)
                #Break the loop

            except Exception as e:                
                break

        
        result = {
            'key':keysize,
            'avg_distance' :sum(distances) / len(distances)
        }

        averages_distances.append(result)

    #Take the 5 shortest average Distances
    possible_key_lengths = sorted(averages_distances,key = lambda x: x['avg_distance'])[:5]

    possible_plaintext = []

    #Iterating through each one of the five results with the shortest
    #Normalized differences

    for res in possible_key_lengths:
        #Will populate with a single characted as each transposed
        #Block has been single byte-XOR brute forced

        key = b''

        for i in range(res['key']):

            #Create a block made up of each nth byte , where n
            #is the keysize

            block = b''

            for j in range(i,len(ciphertext),res['key']):
                block+= bytes([ciphertext[j]])
            
            key += bytes([SingleByteXORCipher(block)['key']])
            possible_plaintext.append((MiscFunctions.XORWithRepeatingKey(ciphertext,key),key))

    return max(possible_plaintext,key = lambda x :MiscFunctions.getEnglishScore(x[0]))