-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchallenge_31.py
159 lines (118 loc) · 4.26 KB
/
challenge_31.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Implement and break HMAC-SHA1 with an artificial timing leak
import sys
import time
from typing import Callable
import numpy as np
from challenge_28 import sha1
def hmac(
key: bytes,
msg: bytes,
hasher: Callable = sha1,
blocksize: int = 64,
rt: type | None = None,
) -> bytes:
"""
HMAC.
Returns HMAC(key, msg, hash_function, blocksize).
For SHA1 this is a 40-character hexdigest (20 bytes).
>>> hmac(b"key", b"The quick brown fox jumps over the lazy dog")
'de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9'
"""
if len(key) > blocksize:
key = hasher(key)
key += b"\x00" * (blocksize - len(key))
ipad = b"\x36" * blocksize
opad = b"\x5c" * blocksize
inner = hasher(xor(key, ipad) + msg, rt=bytes)
outer = xor(key, opad)
return hasher(outer + inner, rt=rt)
def xor(a: bytes, b: bytes) -> bytes:
return bytes([x ^ y for (x, y) in zip(a, b)])
# Server-side
KEY = b"012345ABCDEF"
def verify(msg: bytes, signature: str, key: bytes = KEY) -> bool:
return hmac(key, msg) == signature
def insecure_compare(a: str | bytes, b: str | bytes, ms_delay: int = 50) -> bool:
#
# insecure because it's using an early exit which allows the timing attack
#
# this can trivially be made secure by always looking at _all_ of a and b
#
assert ms_delay >= 0
if len(a) != len(b):
return False
for x, y in zip(a, b):
if x != y:
return False
time.sleep(ms_delay / 1000.0)
return True
def insecure_verify(
msg: bytes, signature: str, key: bytes = KEY, ms_delay: int = 50
) -> bool:
return insecure_compare(hmac(key, msg), signature, ms_delay=ms_delay)
# Client-side
# With 10 ms delay, the simple algorithm starts making mistakes
# after about 6 characters.
# But by just repeating the timing step once, we get better values.
# This breaks again, however, for ms_delay=5 (starts making mistakes
# after solving about half).
#
# If the delay is short then the variance in timings is greater, so
# after a while it will start making mistakes. It may be best to
# measure the stddev in each round and use that for determininig the
# number of repeats to use in the next round. It's kind of hard to fine-tune
# this so that it works for ms_delay < 5. The code below works for ms_delay == 5
# but still makes mistakes for smaller delays.
#
# One way to deal with this, is to let the search run to the end, then
# check, and backtrack. I got tired of the problem so didn't implement this.
def find_mac(path: str = "challenge_31.py", ms_delay: int = 50, max_probes: int = 10):
with open(path, "rb") as f:
msg = f.read()
print(f"Testing ms_delay {ms_delay} ms")
target = hmac(KEY, msg)
print("Actual MAC:", target)
mac = ["0" for _ in range(40)]
sys.stdout.write("Found: ")
sys.stdout.flush()
best = "?"
dt_offset = 0.0
for i in range(40):
for nn in range(max_probes):
timings = np.zeros(16)
for j, c in enumerate("0123456789abcdef"):
mac[i] = c
signature = "".join(mac)
start = time.time()
_ = insecure_verify(msg, signature, KEY, ms_delay=ms_delay)
timings[j] = time.time() - start
# normalize
timings = (timings - timings.mean()) / timings.std()
# find three best indices
k3, k2, k1 = timings.argpartition(-3)[-3:]
best = "0123456789abcdef"[k1]
m1 = timings[k1]
# thresholding
if m1 > 3.6:
break
m2 = timings[k2]
m3 = timings[k3]
timings.sort()
diffs = timings[1:] - timings[:-1]
diffs_avg = diffs.mean()
diffs_std = diffs.std()
d1 = (m1 - m2 - diffs_avg) / diffs_std
d2 = (m2 - m3 - diffs_avg) / diffs_std
if d1 > 2 * d2:
break
mac[i] = best
sys.stdout.write(best)
sys.stdout.flush()
sys.stdout.write("\n")
signature = "".join(mac)
print("Found ", signature)
print("Actual", target)
print("Correct", signature == target)
if __name__ == "__main__":
ms_delay = int(sys.argv[1]) if len(sys.argv) == 2 else 50
find_mac(ms_delay=ms_delay)