<jupyter_start><jupyter_text>Question 1 -- Convert hex to base64<jupyter_code>import codecs

def hex_to_b64(hexa):
    b64 = codecs.encode(codecs.decode(hexa, 'hex'), 'base64').decode()
    return b64

hexa = "<KEY>"
print(hex_to_b64(hexa))<jupyter_output>SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG<KEY>IG11c2hyb29t<jupyter_text>Explaination: This code makes use of the codecs library available in the python to decode hex and later convert it to base64    Question 2 -- Fixed XOR<jupyter_code>def XOR(a,b):
    lookup = {"0" : "0000", "1" : "0001", "2" : "0010", "3" : "0011", "4" : "0100", "5" : "0101", "6" : "0110", "7" : "0111", "8" : "1000", "9" : "1001", "a" : "1010", "b" : "1011", "c" : "1100", "d" : "1101", "e" : "1110", "f" : "1111"}
    lookup2 = {"10":"a","11":"b","12":"c","13":"d","14":"e","15":"f"}
    a= lookup[a]
    b= lookup[b]
    c=""   
    for i in range(4):
        c=c+str(int(a[i]) ^ int(b[i])) 
    if int(c,2)>=10:
        return lookup2[str(int(c,2))]
    else:
        return (str(int(c,2)))   
        
def fixedXOR(x,y):
    result = ""
    for i in range(len(x)):
        result = result + (XOR(x[i],y[i]))
    return result 
       
final = fixedXOR("1c0111001f010100061a024b53535009181c","686974207468652062756c6c277320657965")    
final<jupyter_output><empty_output><jupyter_text>Explaination: In this question we make XOR of 2 hex strings. The fixedXOR function takes in two strings and calls XOR on individual character of the string. XOR function maintains 2 lookup tables to convert single int to 4 bit binary and another for converting integer to hex values. We iterate over 4 bits and perform individual XOR. Later we accumulate the result from XOR and print it.   Question 3 -- Single-byte XOR cipher<jupyter_code>import codecs
cipher="1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"
freqs = {97: 0.0651738,98: 0.0124248,99: 0.0217339,100: 0.0349835,101: 0.1041442,102: 0.0197881,103: 0.0158610,
        104: 0.0492888,105: 0.0558094,106: 0.0009033,107: 0.0050529,108: 0.0331490,109: 0.0202124,110: 0.0564513,
        111: 0.0596302,112: 0.0137645,113: 0.0008606,114: 0.0497563,115: 0.0515760,116: 0.0729357,117: 0.0225134,
        118: 0.0082903,119: 0.0171272,120: 0.0013692,121: 0.0145984,122: 0.0007836,32: 0.1918182}


def xor_str(string1,string2):
    st1=codecs.decode(string1,'hex')
    st2=codecs.decode(string2,'hex')
    x=(bytes(a^b for a,b in zip(st1,st2))).hex()
    return x

def Single_byte_Xor_cipher(cipher):
    index=0
    high=0
    for i in range(0,256):
        if (i<16):
            xor_byte=('0'+hex(i)[2:])*(int)(len(cipher)/2)
        else:
            xor_byte=hex(i)[2:]*(int)(len(cipher)/2)
        new=(codecs.decode(xor_str(cipher,xor_byte),"hex"))
    
        count=0.0
        for j in range(len(new)):
        
            if new[j] in freqs:
                count = count + freqs[new[j]]
            if count>high:
                high=count
                index=i
    return high,index
     
    
def output(cipher,index):
    print("The key to decrypt message is '{}'".format((chr(index) ))) 
    print("The decrypted string is : ",end="")
    print((codecs.decode(xor_str(cipher,hex(index)[2:]*(int)(len(cipher)/2)),"hex")).decode()) 
    
high, index = Single_byte_Xor_cipher(cipher)
output(cipher,index)<jupyter_output>The key to decrypt message is 'X'
The decrypted string is : Cooking MC's like a pound of bacon<jupyter_text>Explaination: In the above code the single_byte_xor_cipher is trying all the possible 256 ascii values in order to fetch the correct key to decrypt cipher. We add '0' to the ascii value under 16 to maintain length constraint. Then we pass both the guesses single character key and cipher to the xor_str function to get the xor. We decide upon the key based on the frequency table that states the frequency of a character in the english langauge. Based on the highest score obtained we print out the gussed single character key.    Question 4 -- Detect single-character XOR<jupyter_code>high = 0
for line in open('input.txt'):
    line = line.strip()
    count,index = Single_byte_Xor_cipher(str(line))
    if high < count:
        high = count
        index1 = index
        line1 = line
        
        
output(line1, index1)<jupyter_output>The key to decrypt message is '5'
The decrypted string is : Now that the party is jumping<jupyter_text>Explaination: Here we make use of the function that we generated in the previous question. Instead of trying a single character on a string we try it on the a file that contains cipher of length 60. Based on the english frequency in previous question we get the above output.  Question 5 -- Implement repeating-key XOR<jupyter_code>import binascii
text = b"""Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal"""
key = "ICE"

def repeating_key_XOR(text,key):
    
    key = (key)*(int)(len(text)/3)
    if len(text)%3!=0:
        for i in range(len(text)%3):
            key = key+key[i]
            
    key=bytes(key,encoding='utf-8')  
    a=text
    b=key
    return (bytes(a^b for a,b in zip(a,b)).hex())
    

print(repeating_key_XOR(text,key))<jupyter_output>0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f<jupyter_text>Explaination : Here we are bringing in the input string in the form of bytes. The function repeating_key_XOR would convert the key equivalent to the length of text. We make the increase in the lenght the same way did it in the question 3. Then we simply XOR the bytes with the help of zip function.  Question 6 -- Break repeating-key XOR<jupyter_code>import base64
import codecs
str1=base64.b64decode("".join(list(open("input6.txt", "r"))))
str_ascii=codecs.encode(str1,'hex')

def humming_distance(str1,str2):
    n=(''.join(["{0:08b}".format(x) for x in str1]))
    m=(''.join(["{0:08b}".format(x) for x in str2]))
    count=0
    for a,b in zip(n,m):
        if a!=b:
            count=count+1
    return count

def optimal_key_len():
    for keysize in range(2,40):
        avg = 0
        for keysize1 in range(len(str1)//keysize):
            a = str1[keysize*keysize1:keysize*keysize1+keysize]
            b = str1[keysize*keysize1+keysize:(keysize*keysize1+keysize)+keysize]
            norm_dist = float(humming_distance(a,b)/keysize)
            avg = avg + norm_dist
        d = keysize , avg/(len(str1)//keysize)
        c.append(d)
    return min(c, key = lambda t: t[1])

humming_distance(b"this is a test",b"wokka wokka!!!")

def divide_with_keysize():
    chunks=[]
    print()
    for keylen in range(len(str1)//29):
        chunks.append(str1[keylen*29:keylen*29+29])
    return chunks

chunks = divide_with_keysize()  
def guess_key():
    key=''
    for i in range(29):
        new=bytearray()

        for tup in chunks:
            new.append(tup[i])
        a,b=Single_byte_Xor_cipher(binascii.hexlify(new[:len(new)]).decode())    
        key+=chr(b)
    return (key)

print (guess_key())

def decrypt(key):
    final_str=repeating_key_XOR(str1,key)
    print(binascii.unhexlify(final_str).decode())
    
decrypt(guess_key())<jupyter_output>Terminator X: Bring the noise
I'm back and I'm ringin' the bell 
A rockin' on the mike while the fly girls yell 
In ecstasy in the back of me 
Well that's my DJ Deshay cuttin' all them Z's 
Hittin' hard and the girlies goin' crazy 
Vanilla's on the mike, man I'm not lazy. 

I'm lettin' my drug kick in 
It controls my mouth and I begin 
To just let it flow, let my concepts go 
My posse's to the side yellin', Go Vanilla Go! 

Smooth 'cause that's the way I will be 
And if you don't give a damn, then 
Why you starin' at me 
So get off 'cause I control the stage 
There's no dissin' allowed 
I'm in my own phase 
The girlies sa y they love me and that is ok 
And I can dance better than any kid n' play 

Stage 2 -- Yea the one ya' wanna listen to 
It's off my head so let the beat play through 
So I can funk it up and make it sound good 
1-2-3 Yo -- Knock on some wood 
For good luck, I like my rhymes atrocious 
Supercalafragilisticexpialidocious 
I'm an effect and that you can bet 
I can take [...]<jupyter_text>Explaination: Here in this quesiton I took the 3rd approach as per mentioned in the instructions. First of all I read the file and find the key length using the average of the normalized humming distance. Then we divide the input string in to chunks of 29 which is the optimal key length that we guessed in the previous step. After we have a list of chunks we take transpose of the whole list so that we have 29 different strings with first character of all the strings. Then we use the single_byte_xor_cipher function the we created in the previous questions to guess the single byte of key. Doing this for 29 times would give us the appropriate string key. late we decode the whole file.  Question 7 -- AES in ECB mode<jupyter_code>from base64 import b64decode
from Crypto.Cipher import AES

BLOCK_SIZE = 16  # Bytes
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

def change(st):
    st = st + (BLOCK_SIZE - len(st) % BLOCK_SIZE) * bytes(chr(BLOCK_SIZE - len(st) % BLOCK_SIZE),encoding = "utf-8")
    return st    
    
def decrypt(enc):
        
        enc = b64decode(enc)
        #enc = change(enc)
        #print (enc)
        cipher = AES.new(key, AES.MODE_ECB)
        return unpad(cipher.decrypt(enc)).decode('utf8',"ignore")

def get_file(file):
    st=""
    for line in open(file):
        line = line.strip()
        st = st + line
    return st

key = "YELLOW SUBMARINE"  
print(decrypt(get_file("input7.txt")))<jupyter_output>I'm back and I'm ringin' the bell 
A rockin' on the mike while the fly girls yell 
In ecstasy in the back of me 
Well that's my DJ Deshay cuttin' all them Z's 
Hittin' hard and the girlies goin' crazy 
Vanilla's on the mike, man I'm not lazy. 

I'm lettin' my drug kick in 
It controls my mouth and I begin 
To just let it flow, let my concepts go 
My posse's to the side yellin', Go Vanilla Go! 

Smooth 'cause that's the way I will be 
And if you don't give a damn, then 
Why you starin' at me 
So get off 'cause I control the stage 
There's no dissin' allowed 
I'm in my own phase 
The girlies sa y they love me and that is ok 
And I can dance better than any kid n' play 

Stage 2 -- Yea the one ya' wanna listen to 
It's off my head so let the beat play through 
So I can funk it up and make it sound good 
1-2-3 Yo -- Knock on some wood 
For good luck, I like my rhymes atrocious 
Supercalafragilisticexpialidocious 
I'm an effect and that you can bet 
I can take a fly girl and make her wet. 
[...]<jupyter_text>Explaination: Here in this question we need to implement the AES in ECB mode which means the cipher goes through single step of encryption. Our key YELLOW SUBMARINE is a 16 byte string but we need to pad in the input cipher. We created pad and unpad functions to do the above mentioned work. Then we implement AES using new() functions mentioning ECB as mode. We simply decrypt the file from base64 and convert it into ascii before printing.  Question 8 -- Detect AES in ECB mode<jupyter_code>from collections import defaultdict

max_reps = 0
cipher = None

def substring(buffer, block_length=16):
    count = defaultdict(lambda: -1)
    for i in range(0, len(buffer), block_length):
        block = bytes(buffer[i:i + block_length])
        count[block] += 1
    return sum(count.values())


def detect_aes(max_reps = 0,cipher = None):
    for ciphertext in list(open("input8.txt", "r")):
        ciphertext = ciphertext.rstrip()
        count = substring(bytearray(ciphertext,'utf-8'))
        if count > max_reps:
            max_reps = count
            cipher = ciphertext
    print("The key with AES is :"+cipher)
 
detect_aes()<jupyter_output>The key with AES is :d880619740a8a19b7840a8a31c810a3d08649af70dc06f4fd5d2d69c744cd283e2dd052f6b641dbf9d11b0348542bb5708649af70dc06f4fd5d2d69c744cd2839475c9dfdbc1d46597949d9c7e82bf5a08649af70dc06f4fd5d2d69c744cd28397a93eab8d6aecd566489154789a6b0308649af70dc06f4fd5d2d69c744cd283d403180c98c8f6db1f2a3f9c4040deb0ab51b29933f2c123c58386b06fba186a<jupyter_text>Explaination: In this question we simply make use of dictionary implementation in python to recognize the longest substing in a cipher. By adding the key into the dictionary, it would save the count of times the string is repeated. Here again we mention the block_size as 16 as the total width of a cipher with aes would be in the blocks of length 16. The string from the file is read in the form of bytarray to be saved in the dictionary.  Question 9 -- Implement PKCS7 padding<jupyter_code>BLOCK_SIZE = 20
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
    
pad("YELLOW SUBMARINE")<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup