-
Notifications
You must be signed in to change notification settings - Fork 0
/
challenge_12.py
119 lines (88 loc) · 3.07 KB
/
challenge_12.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Byte-at-a-time ECB decryption (Simple)
from base64 import b64decode
from os import urandom
import random
from typing import Callable
from Crypto.Cipher import AES
from challenge_09 import pkcs7
random.seed(20240401)
DEBUG = False
MYSTERY_TEXT = """
Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg
aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq
dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg
YnkK
"""
def encryption_oracle(key: bytes = b"") -> tuple[str, Callable]:
key = key or urandom(16)
secret = b64decode(MYSTERY_TEXT.strip())
def oracle(plaintext: str | bytes) -> bytes:
if isinstance(plaintext, str):
plaintext = plaintext.encode("utf-8")
plaintext = pkcs7(plaintext + secret)
cipher = AES.new(key, AES.MODE_ECB)
return cipher.encrypt(plaintext)
return "ECB", oracle
def uncover_secret():
_, oracle = encryption_oracle()
blocksize = uncover_blocksize(oracle)
if blocksize is None:
raise NotImplementedError(
"Failed to determine block-size. Oracle is not using AES-ECB."
)
secret_length = uncover_target_length(oracle, blocksize)
secret = ""
step = 1
while True:
if len(secret) >= secret_length:
break
next_letter = probe(oracle, secret, blocksize)
if not next_letter:
break
secret += next_letter
if DEBUG:
print(f"[{step}] {secret!r}")
step += 1
return secret
def uncover_blocksize(oracle: Callable) -> int | None:
for blocksize in range(8, 128):
plaintext = "A" * blocksize * 2
ciphertext = oracle(plaintext)
if ciphertext[0:blocksize] == ciphertext[blocksize : 2 * blocksize]:
return blocksize
return None
def uncover_target_length(oracle: Callable, blocksize: int) -> int:
n = len(oracle(""))
res = [len(oracle(" " * k)) for k in range(0, blocksize)]
if len(set(res)) == len(res):
return n
m = n
for length in res:
if length > n:
break
m -= 1
return m
def probe(oracle: Callable, found: str, blocksize: int) -> str:
# make a probe that is able to contain everything found so far
# and then still has exactly one more free cell
nblocks = 1 + (len(found) // blocksize)
# point j to the last prefix block in the probes
j = blocksize * (nblocks - 1)
k = nblocks * blocksize - len(found) - 1
prefix = [ord("A")] * k
suffix = [ord(c) for c in found]
# let the oracle fill in the last letter of the prefix block
# with the next, not-yet-discovered letter of the secret
target = oracle(bytes(prefix))
# run a brute-force attack to see which byte matches the hidden letter
for letter in range(256):
ciphertext = oracle(bytes(prefix + suffix + [letter]))
if ciphertext[j : j + blocksize] == target[j : j + blocksize]:
return chr(letter)
return ""
if __name__ == "__main__":
secret = uncover_secret()
print()
print("The uncovered text is:")
print()
print(secret)