This exercise, Break repeating-key XOR, combines all of the tools acquired so far in completing the first set of challenges:
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Created on Wed Sep 25 19:47:03 2019@author: dwrob""""""Break repeating-key XORIt is officially on, now.This challenge isn't conceptually hard, but it involves actual error-prone coding.The other challenges in this set are there to bring you up to speed. This one is there to qualify you. If you can do this one, you're probably just fine up to Set 6.There's a file here. It's been base64'd after being encrypted with repeating-key XOR.Decrypt it.Here's how:Let KEYSIZE be the guessed length of the key; try values from 2 to (say) 40.Write a function to compute the edit distance/Hamming distance between two strings.The Hamming distance is just the number of differing bits. The distance between:this is a testandwokka wokka!!!is 37. Make sure your code agrees before you proceed.For each KEYSIZE, take the first KEYSIZE worth of bytes, and the second KEYSIZE worth of bytes,and find the edit distance between them. Normalize this result by dividing by KEYSIZE.The KEYSIZE with the smallest normalized edit distance is probably the key. You could proceedperhaps with the smallest 2-3 KEYSIZE values. Or take 4 KEYSIZE blocks instead of 2 and averagethe distances.Now that you probably know the KEYSIZE: break the ciphertext into blocks of KEYSIZE length.Now transpose the blocks: make a block that is the first byte of every block, and a block thatis the second byte of every block, and so on.Solve each block as if it was single-character XOR. You already have code to do this.For each block, the single-byte XOR key that produces the best looking histogram is therepeating-key XOR key byte for that block. Put them together and you have the key.This code is going to turn out to be surprisingly useful later on. Breaking repeating-key XOR("Vigenere") statistically is obviously an academic exercise, a "Crypto 101" thing. But more people"know how" to break it than can actually break it, and a similar technique breaks something muchmore important.No, that's not a mistake.We get more tech support questions for this challenge than any of the other ones. We promise,there aren't any blatant errors in this text. In particular: the "wokka wokka!!!" edit distancereally is 37."""import base64, stringdebug = 0ASCII = string.printableprint("\nASCII characters to test:",ASCII,"\n")ct64 = """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"""# https://www.base64decode.net/python-base64-b64decodect_bytes = bytearray(base64.b64decode(ct64))print("Input ciphertext ( type =",type(ct_bytes),"):\n")print(ct_bytes,"\n\n")#>>> key = 37#>>> message = bytearray(b"Hello World")#>>> s = bytearray(x ^ key for x in message)def xor_byte_arrays(array_1, array_2):""" XOR two bytearrays """# if len(array_1) != len(array_2):# raise ValueError("Undefined for sequences of unequal length")return bytes(b1 ^ b2 for b1, b2 in zip(array_1, array_2))def multibyte_key_encrypt_decrypt(input_message, key):input_message_len = len(input_message)key_len = len(key)chunks = divmod(input_message_len,key_len)fullblocks = chunks[0]shortblock = chunks[1]if shortblock > 0:fullblocks = fullblocks + 1# In the loop, when you get to the last block, if there is a remainder, work will have to be doneoutput = bytearray("","Latin-1")print("Key:",key)count = 0while (count < fullblocks):print("\nCount:",count)# Bytearray slices:# block_0 = pt[:3]# block_1 = pt[3:6]# block_2 = pt[6:9]# Etc.# https://stackoverflow.com/questions/509211/understanding-slice-notationinput_block = input_message[key_len*count:key_len*(count+1)]output_block = xor_byte_arrays(key,input_block)count = count + 1# build the output stringoutput = output + output_blockprint("Input block:",input_block)print("XORed output:",output_block)# print("Cumulative output as hex bytes:\n",binascii.hexlify(output))return outputdef calculate_score(xor_output_bytes):""" Score possible plaintexts created by single-character XOR """if debug == 1:print("\n\n######################################\nScore plaintexts created by single-character XOR\n######################################")xor_output_string = xor_output_bytes.decode("utf-8")local_score = 0# https://en.wikipedia.org/wiki/Letter_frequencychar = "e"weight = 12.7local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "t"weight = 9.06local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "a"weight = 8.17local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "o"weight = 7.51local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "i"weight = 6.97local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "n"weight = 6.75local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "s"weight = 6.33local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "h"weight = 6.09local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "r"weight = 5.99local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "d"weight = 4.23local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "l"weight = 4.02local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "c"weight = 2.78local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "u"weight = 2.76local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "m"weight = 2.41local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "w"weight = 2.36local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "f"weight = 2.23local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "g"weight = 2.02local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "y"weight = 1.97local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "p"weight = 1.93local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "b"weight = 1.49local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "v"weight = 0.978local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "k"weight = 0.772local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")# local_score += xor_output_string.count("j") * 0.153# local_score += xor_output_string.count("x") * 0.150# local_score += xor_output_string.count("q") * 0.095# local_score += xor_output_string.count("z") * 0.074char = "E"weight = 12.7local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "T"weight = 9.06local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "A"weight = 8.17local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "O"weight = 7.51local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "I"weight = 6.97local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "N"weight = 6.75local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "S"weight = 6.33local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "H"weight = 6.09local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "R"weight = 5.99local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "D"weight = 4.23local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "L"weight = 4.02local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "C"weight = 2.78local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "U"weight = 2.76local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "M"weight = 2.41local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "W"weight = 2.36local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "F"weight = 2.23local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "G"weight = 2.02local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "Y"weight = 1.97local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "P"weight = 1.93local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "B"weight = 1.49local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "V"weight = 0.978local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "K"weight = 0.772local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")# local_score += xor_output_string.count("J") * 0.153# local_score += xor_output_string.count("X") * 0.150# local_score += xor_output_string.count("Q") * 0.095# local_score += xor_output_string.count("Z") * 0.074# https://linguistics.stackexchange.com/questions/12324/special-characters-and-uppercase-frequencychar = ","weight = 2.38local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "."weight = 2.13local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "\""weight = 0.7local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")# local_score += xor_output_string.count("\)") * 0.169# local_score += xor_output_string.count("(") * 0.169# local_score += xor_output_string.count("?") * 0.162# local_score += xor_output_string.count(":") * 0.136# Out of my own headchar = " "weight = 30local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "0"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "1"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "2"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "3"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "4"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "5"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "6"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "7"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "8"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")char = "9"weight = 2local_score += xor_output_string.count(char) * weightif debug == 1 and xor_output_string.count(char) > 0:print("**** Debug (",char,")",xor_output_string.count(char),"*",weight,"=",xor_output_string.count(char) * weight,"****")if debug == 1: print("\n**** Debug Total raw score:",local_score,"****\n")return local_scoredef scan_for_keys():""" Find Hamming distances for possible keys. We are looking for a repeating similaritybetween blocks of a certain length, starting at the beginning of the original ciphertext.Block length yielding most-similar blocks (lowest Hamming Distance) is the probable key length."""print("\n\n####################################\nBegin searching for likely key size\n####################################\n\n")min_key = 2max_key = 40tally = []for key_size in range(min_key,max_key):block_check = divmod(len(ct_bytes),key_size)blocks = block_check[0]if block_check[1] > 0: blocks -= 1if blocks % 2 > 0: blocks -= 1hd = 0print("\nFor",key_size,"bytes:", blocks,"blocks (",blocks/2,"pairs to XOR )")for i in range(0, blocks, 2):""" iterate through pairs of blocks """test_block_1 = ct_bytes[key_size*i:key_size*(i+1)]test_block_2 = ct_bytes[key_size*(i+1):key_size*(i+2)]xor_output_bytes = xor_byte_arrays(test_block_1,test_block_2)xor_output_bin = ''.join(format(i, '#010b') for i in xor_output_bytes)""" Hamming Distance hd is given by countingthe ones in the XOR output of two binary strings."""hd += xor_output_bin.count("1")local_blockpair_hd = xor_output_bin.count("1")if debug == 1: print("Local blockpair HD:",local_blockpair_hd,"for",xor_output_bin)# print("Block size",len(test_block_1),"bytes:",test_block_1)pairs = blocks/2average = hd/pairsnormalized = average/key_sizeif debug == 1:print("Raw DH score:",hd)print("Blocks:",blocks)print("Pairs:",pairs)print("Average of",blocks/2,"outputs:",average)print("Average HD normalized for key_size (",key_size,"):",normalized)new_result = { "key_size": key_size,"normalized_HD": normalized }print("Key size:",key_size, "HD:","Normalized HD:","{0:.1f}".format(normalized),"\n")tally.append(new_result)ranked = sorted(tally, key = lambda k: k["normalized_HD"],reverse=False)detected_key_size = ranked[0].get("key_size")norm = ranked[0].get("normalized_HD")print("\n\nHamming distance for all key lengths, ranked\n")for i in ranked:print(i)print("\n************* Detected key size:",detected_key_size,"*************")print("\nNormalized HD:","{0:.1f}".format(norm),"\n")return detected_key_sizedef process_ct_blocks(ct_bytes, detected_key_size):""" Split ct into sequential key-sized blocks, then reshuffle the bytes into target blocks """print("\n\n############################\nBegin processing blocks\n############################\n\n")ct_len = len(ct_bytes)key_len = detected_key_sizechunks = divmod(ct_len,key_len)fullblocks = chunks[0]shortblock = chunks[1]""" Account for a short block at the end"""if shortblock > 0:fullblocks = fullblocks + 1""" Initialize lists to contain n target blocks where n = key_len.Iterate by key_len blocks through ct_bytes and add each byte toone of n collation lists named for its key byte index.For a four-byte key:Byte position: 0 ---> collation[0]1 ---> collation[1]2 ---> collation[2]3 ---> collation[3]4 ---> collation[0]5 ---> collation[1]6 ---> collation[2]7 ---> collation[3] (iterate through ct_bytes...)Each collation has length len(ct_bytes)/nThe approach below to dynamic variables creates a list of listswith indices matching key_len bytes."""key_position_collations = [[] for i in range(key_len)]count = 0while (count < fullblocks):""" Counting from zero....The slicing here works because the range of key_len is 0 to keylen-1,so the value key_len itself is excluded from the slice."""input_block = bytes(ct_bytes[key_len*count:key_len*(count+1)])if debug == 1:print("\n\n######################################\nOuter loop, iterating through blocks\n######################################")print("\nBlock count:",count)print("Raw input_block:",input_block)for i in range(key_len):key_position_collations[i].append(input_block[i:i+1])if debug == 1:print("\n\n #########################################################################\n Inner loop, iterating through characters and inserting them in lists\n #########################################################################")print("\n\n Position",i,":",input_block[i:i+1])print(" Append byte to List",i)count = count + 1if debug == 1:print("\n\n#####################################################\nCollations completed; display",key_len,"collations\n#####################################################\n")for i in range(key_len):print("Collation for Key Byte", i,"\n\n",key_position_collations[i],"\n\n")return key_position_collationsdef main():detected_key_size = scan_for_keys()# detected_key_size = 29key_byte_candidates = [[] for i in range(detected_key_size)]ranked_candidates = [[] for i in range(detected_key_size)]key_position_collations = process_ct_blocks(ct_bytes, detected_key_size)for i in range(detected_key_size):for chr in (ASCII):key = bytearray(chr * len(key_position_collations[i]), "Latin-1")""" We must convert the key and the list to byte objects """list2ba = b''.join(x for x in key_position_collations[i])pt_candidate = xor_byte_arrays(key,list2ba)if debug == 1:print("Key type:",type(key))print("Key character:", chr)print("Key =",chr,"*",len(key_position_collations[i]))print("Key:\n\n",key)print("\n\nPT candidate type:",type(pt_candidate))print("PT candidate:\n\n",pt_candidate,"\n\n")score = calculate_score(bytes(pt_candidate))if debug == 1: print("Score:","{0:.0f}".format(score),"\n\n")""" Store dictionaries in the prepared list for this byte iteration """new_result = { "position": i, "character": chr, "score": score}key_byte_candidates[i].append(new_result)if debug == 1: print("\n\nUnranked lists:\n\n",key_byte_candidates)""" Rank each of the lists by score """derived_key = []for i in range(detected_key_size):print("\n\n#####################################################\nRanked collation",i,"\n#####################################################\n")ranked_candidates[i] = sorted(key_byte_candidates[i], key = lambda k: k["score"],reverse=True)if debug == 1:for j in range(len(ASCII)):ascii_char = ranked_candidates[i][j].get("character")print("\nASCII Char:",ascii_char,"[",hex(ord(ascii_char)),"]")score = ranked_candidates[i][j].get("score")print("Score:","{0:.0f}".format(score))winning_byte = ranked_candidates[i][0].get("character")derived_key.append(winning_byte)""" Key is a list; convert it to something usable """x = ''.join(x for x in derived_key) # yields a stringy = x.encode('utf-8') # yields a byte objectz = y.hex() # yields a hex bytes stringw = bytearray(x, "utf-8")print("\n\nRepresentations of the Derived Key:\n")print("String x from applying join to the list:",type(x),x,"\n")print("Bytes object y from encoding string x to UTF-8:",type(y),y,"\n")print("String z from applying the hex attribute to byte object y:",type(z),z,"\n")print("And here is a bytearray w from hex string z:",type(w),w,"\n")# decryption = multibyte_key_encrypt_decrypt(ct_bytes, bytes("Terminator X: Bring the noise","Latin-1"))decryption = multibyte_key_encrypt_decrypt(ct_bytes, y)print("\n\nDecryption in raw bytes:\n\n",decryption)print("\n\nFormatted decryption:\n\n",decryption.decode())main()