Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/attacks/otp/key_reuse.py
2589 views
1
from math import log10
2
import string
3
4
5
def _hamming_distance(a, b):
6
distance = 0
7
for x, y in zip(a, b):
8
distance += bin(x ^ y).count("1")
9
10
return distance
11
12
13
def _guess_key_sizes(c: list[bytes], max_key_size):
14
key_sizes = []
15
prev_distance = None
16
for key_size in range(2, max_key_size + 1):
17
blocks = []
18
for ci in c:
19
j = 0
20
while (j + 1) * key_size <= len(ci):
21
blocks.append(ci[j * key_size:(j + 1) * key_size])
22
j += 1
23
24
if len(blocks) < 2:
25
continue
26
27
distance = 0
28
for i in range(len(blocks) - 1):
29
distance += _hamming_distance(blocks[i], blocks[i + 1])
30
31
distance /= len(blocks) - 1
32
distance /= key_size
33
if prev_distance is not None:
34
diff = prev_distance - distance
35
36
key_sizes.append((key_size, diff))
37
38
prev_distance = distance
39
40
return [x[0] for x in sorted(key_sizes, key=lambda x: x[1], reverse=True)]
41
42
43
def _score(p, char_frequencies, char_floor):
44
score = 0
45
for b in p:
46
c = chr(b)
47
if not (c in string.printable or c in string.whitespace):
48
return None
49
50
c = c.lower()
51
if c in char_frequencies:
52
score += log10(char_frequencies[c])
53
else:
54
score += char_floor
55
56
return score
57
58
59
def _transpose(c, i, key_size):
60
transposed = bytearray()
61
for c in c:
62
j = 0
63
while j + i < len(c):
64
transposed.append(c[j + i])
65
j += key_size
66
67
return transposed
68
69
70
def _frequency_analysis(c, char_frequencies, char_floor):
71
max_score = float("-inf")
72
candidate_k = None
73
for k in range(256):
74
p = bytes([b ^ k for b in c])
75
score = _score(p, char_frequencies, char_floor)
76
if score is not None and score > max_score:
77
max_score = score
78
candidate_k = k
79
80
return candidate_k
81
82
83
def attack(c, char_frequencies, char_floor, key_size=None):
84
"""
85
Breaks the one-time pad when the key is reused in a single plaintext or multiple plaintexts.
86
Note: this implementation is very primitive and only for educational purposes. For more real-world analysis, use xortool.
87
:param c: the list of ciphertexts
88
:param char_frequencies: a dict of (char, frequency) items for the plaintext language
89
:param char_floor: the value to assign to a character if it is not found in char_frequencies
90
:param key_size: the size of the key in bytes (default: None): if no key size is given, this method attempts to discover it using the Hamming distance
91
:return: the best guess for the key
92
"""
93
if key_size is None:
94
key_sizes = _guess_key_sizes(c, max(map(lambda ci: len(ci), c)))
95
else:
96
key_sizes = [key_size]
97
98
for key_size in key_sizes:
99
k = bytearray(key_size)
100
for i in range(key_size):
101
transposed = _transpose(c, i, key_size)
102
candidate_k = _frequency_analysis(transposed, char_frequencies, char_floor)
103
if candidate_k is None:
104
break
105
else:
106
k[i] = candidate_k
107
else:
108
return k
109
110