-
Notifications
You must be signed in to change notification settings - Fork 0
/
S01C06.py
85 lines (65 loc) · 2.44 KB
/
S01C06.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# Imports
from base64 import b64decode
from lib import *
def hamming_distance(text1: bytes, text2: bytes) -> int:
"""
Calculates the Hamming Distance between the given byte strings.
"""
distance = 0
dec_list = [b1 ^ b2 for b1, b2 in zip(text1, text2)]
for decimal in dec_list:
distance += bin(decimal).count("1")
if len(text1) > len(text2):
diff = len(text1) - len(text2)
text = text1
else:
diff = len(text2) - len(text1)
text = text2
for i in range(1, diff+1):
distance += bin(text[-i]).count("1")
return distance
def break_repeated_xor_keysize(ciphertext: bytes) -> int:
"""
Approximates the keysize based on the hamming distance between different blocks of ciphertexts.
Returns the keysize with least hamming distance between consecutive sets of ciphertext.
"""
keysize = 0
min_distance = 100000
for key in range(2, 41):
edit_distance = 0
blocks = [ciphertext[i*key:(i+1)*key] for i in range(4)]
for i in range(0, len(blocks)):
for j in range(0, len(blocks)):
edit_distance += hamming_distance(blocks[i], blocks[j])
normalized_distance = edit_distance/key
if normalized_distance < min_distance:
min_distance = normalized_distance
keysize = key
return keysize
def main():
# Given
inf = open("6.txt", "r")
b64_data = inf.read()
byte_data = b64decode(b64_data)
# Creates blocks of ciphertext in preparation of brute forcing the xor key.
keysize = break_repeated_xor_keysize(byte_data)
cipher_blocks = [byte_data[i:i+keysize] for i in range(0, len(byte_data), keysize)]
#To remove the last block with less characters.
cipher_blocks.pop()
cipher_block_size = len(cipher_blocks[0])
# Brute force the key, one letter at a time.
key = ""
for i in range(0, cipher_block_size):
single_xor_block = b""
# Construct blocks out of a fixed index from all cipher blocks.
for block in cipher_blocks:
single_xor_block += (block[i]).to_bytes(1, "big")
# Apply frequency analysis to the block associated with this index.
result = single_byte_xor_score(single_xor_block)
testkey = result["key"]
key += testkey
print("Key:\n", key)
print("\n\nDeciphered text:\n", repeated_xor(byte_data, key.encode()).decode("utf-8").strip())
return
if __name__=="__main__":
main()