Challenges in Cryptopals Set1

Prologue

Cryptopals : https://cryptopals.com/

This set is marked as a “qualifying” and “relatively easy” set. Let’s have a try.

Convert hex to base64

challenge

The string:

49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d

Should produce:

SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

So go ahead and make that happen. You’ll need to use this code for the rest of the exercises.

solution

import base64
raw='49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
res='SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t'
print(base64.b64encode(raw.decode("hex"))==res)

Fixed XOR

challenge

Write a function that takes two equal-length buffers and produces their XOR combination.

If your function works properly, then when you feed it the string:

1c0111001f010100061a024b53535009181c

… after hex decoding, and when XOR’d against:

686974207468652062756c6c277320657965

… should produce:

746865206b696420646f6e277420706c6179

solution

raw=  '1c0111001f010100061a024b53535009181c'
x =   '686974207468652062756c6c277320657965'
res = '746865206b696420646f6e277420706c6179'
x   = int("0x"+x,16)
raw = int("0x"+raw,16)
result = raw ^ x
print(hex(result)=="0x"+res+"L")

Single-byte XOR cipher

challenge

The hex encoded string:

1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736

… has been XOR’d against a single character. Find the key, decrypt the message.

You can do this by hand. But don’t: write code to do it for you.

How? Devise some method for “scoring” a piece of English plaintext. Character frequency is a good metric. Evaluate each output and choose the one with the best score.

solution


raw = '1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736'
raw = raw.decode("hex")
t="abcdefghijklmnopqrstuvwxyz"
t+=t.upper()
for i in t:
    flag=''
    for _ in raw:
        flag+= chr(ord(_)^ord(i))
    print(flag)

Detect single-character XOR

challenge

One of the 60-character strings in this file has been encrypted by single-character XOR.

Find it.

Solution

data='''
0e3647e8592d35514a081243582536ed3de6734059001e3f535ce6271032
...
32042f46431d2c44607934ed180c1028136a5f2b26092e3b2c4e2930585a
'''
data=data.strip().split("\n")
for _ in data:
    _ = _.decode("hex")
    for i in range(0,127):
        flag=''
        for j in _:
            cur = chr(ord(j)^i)
            if ord(cur) > 127:   
                flag=''
                break
            else:
                flag+=cur
        if(flag!=''):
            print(flag)
# Now that the party is jumping

Implement repeating-key XOR

challenge

Here is the opening stanza of an important work of the English language:

Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal

Encrypt it, under the key “ICE”, using repeating-key XOR.

In repeating-key XOR, you’ll sequentially apply each byte of the key; the first byte of plaintext will be XOR’d against I, the next C, the next E, then I again for the 4th byte, and so on.

It should come out to:

0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272
a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f

Encrypt a bunch of stuff using your repeating-key XOR function. Encrypt your mail. Encrypt your password file. Your .sig file. Get a feel for it. I promise, we aren’t wasting your time with this.

solution

plaintext='''Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal'''
key="ICE"
res=''
aim='0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f'
for x in range(len(plaintext)):
    res+="%02x"%(ord(plaintext[x])^ord(key[x%len(key)]))
print(res==aim)

Break repeating-key XOR

Challenge

There’s a file here. It’s been base64’d after being encrypted with repeating-key XOR.

Solution

Use Hamming Distance to get the possible key-length.

Why Hamming Distance doesn’t change after XOR

IF:
	Hamming_Distance(A,B)== X 
Then, We can get:
	Bin(ord(A)^ord(B))[2:].count("1") == X
Then:
	Hamming_Distance(A^C,B^C)
=	Bin(ord(A)^ord(B)^ord(C)^ord(C))[2:].count("1")
= Bin(ord(A)^ord(B))[2:].count("1")
= X

Get the plaintext by using the solution in single-character XOR.

a='this is a test'
b='wokka wokka!!!'
from pwn import *
import base64
def str_2_bin(a):
    res=''
    for x in a:
        #print(x)
        res+=bin((x))[2:].rjust(8,'0')
    return res
def hamming_dis(a,b):
    bin_a= str_2_bin(a)
    bin_b= str_2_bin(b)
    if(len(bin_a)!=len(bin_b)):
        exit()
    l= len(bin_a)
    res=0
    for x in range(l):
        res+=ord(bin_a[x])^ord(bin_b[x])
    return res
def get_raw():
    f=open("./6.txt")
    data=f.read()
    f.close()
    return data
def cal(str_arry,block_size):
    str_arry=str_arry[:len(str_arry)-(len(str_arry)%block_size)]
    res=0
    t=len(str_arry)//block_size-1
    #print(t)
    if t <= 5:
        return False
    for x in range(5):
        res+=hamming_dis(str_arry[block_size*x:block_size*(x+1)],str_arry[block_size*(x+1):block_size*(x+2)])
    return res/5/block_size
def get_idx(data,idx,m):
    res=""
    for x in range(len(data)):
        if(x%m==idx):
            res+=chr(data[x])
    return res
def do_cal(raw):
    for x in range(2,40):
        print(cal(raw,x),x)
def dec(idx_data):
    groups=[]
    for _ in idx_data:
        res=-1
        max_pos=-1
        for kn in range(256):
            l=len(_)
            pos=0
            for y in _:
                if(chr(ord(y)^kn) in table):
                        pos+=1
            pos=pos/l
            if(pos > max_pos):
                max_pos=pos
                res=kn
        tmp=''
        if(max_pos>0.90):
            for y in _:
                tmp+=chr(ord(y)^res)
            groups.append(tmp)
    if(groups==[]):
        return None
    return groups
if __name__ == '__main__':
    raw= base64.b64decode(get_raw())
    do_cal(raw)
    res=[x for x in range(2,41)]
    table='abcdefghijklmnopqrstuvwxyz'
    table+=table.upper()
    table+="0123456789 '.,"
    for x in res:
        flag=''
        idx_data=[]
        for y in range(x):
            idx_data.append(get_idx(raw,y,x))
        plaintext=dec(idx_data)
        if plaintext == None:
            continue
        for x in range(1,len(plaintext)):
            if(len(plaintext[x])<len(plaintext[0])):
                plaintext[x]=plaintext[x]+'\0'
        for idx in range(len(plaintext[0])):
            for c in plaintext:
                flag+=c[idx]
        print(flag)

AES in ECB mode

Challenge

The Base64-encoded content in this file has been encrypted via AES-128 in ECB mode under the key:”YELLOW SUBMARINE”

Solution

from Crypto.Cipher import AES
import base64
def get_data():
    f=open("./7.txt")
    data=f.read()
    data=base64.b64decode(data)
    f.close()
    return data
key=b"YELLOW SUBMARINE"
aes= AES.new(key,AES.MODE_ECB)
data=get_data()
print(aes.decrypt(data))

Detect AES in ECB mode

In my view, this challenge doesn’t give the info that there are some same blocks in plaintext

Challenge

In this file are a bunch of hex-encoded ciphertexts.

One of them has been encrypted with ECB.

Detect it.

Solution

with open("./8.txt") as fp:
    data=[l.replace("\n",'') for l in fp.readlines()]
import re
for x in data:
    b=re.findall(".{32}",x)
    if(len(b)!=len(set(b))):
        print(x)