Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/attacks/rsa/partial_key_exposure.py
2589 views
1
import logging
2
import os
3
import sys
4
from math import ceil
5
from math import gcd
6
from math import sqrt
7
8
from sage.all import QQ
9
from sage.all import RR
10
from sage.all import ZZ
11
from sage.all import Zmod
12
from sage.all import is_prime
13
14
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))
15
if sys.path[1] != path:
16
sys.path.insert(1, path)
17
18
from attacks.factorization import known_phi
19
from shared.hensel import hensel_roots
20
from shared.small_roots import blomer_may
21
from shared.small_roots import ernst
22
from shared.small_roots import howgrave_graham
23
24
25
def _bdf_corollary_1(e, f, N, m, t, X):
26
for x0, in howgrave_graham.modular_univariate(f, N, m, t, X):
27
p = int(f(x0))
28
if 1 < p < N and N % p == 0:
29
q = N // p
30
phi = (p - 1) * (q - 1)
31
yield p, q, pow(e, -1, phi)
32
33
34
def _bdf_theorem_6(N, e, d_bit_length, d1, d1_bit_length):
35
d0 = d1 << (d_bit_length - d1_bit_length)
36
k_ = (e * d0 - 1) // N
37
logging.info("Generating solutions for k candidates...")
38
for k in range(k_ - 40, k_ + 40):
39
yield d0, k
40
41
42
def _bdf_3(N, e, d_bit_length, d0, d0_bit_length, r, m, t):
43
n = N.bit_length()
44
logging.info(f"Trying {m = }, {t = }...")
45
p = ZZ["p"].gen()
46
x = Zmod(N)["x"].gen()
47
X = int(2 * RR(N) ** (1 / 2) / r) # Equivalent to 2^(n / 2 + 1) / r
48
logging.info("Generating solutions for k candidates...")
49
for k in range(1, e):
50
f = k * p ** 2 + (e * d0 - (1 + k * (N + 1))) * p + k * N
51
for p0 in hensel_roots(f, 2, d0_bit_length):
52
f = x * r + p0
53
for p_, q_, d_ in _bdf_corollary_1(e, f, N, m, t, X):
54
return p_, q_, d_
55
56
return None
57
58
59
def _bdf_4_1(N, e, d_bit_length, d1, d1_bit_length, m, t):
60
logging.info(f"Trying {m = }, {t = }...")
61
p = Zmod(e)["p"].gen()
62
x = Zmod(N)["x"].gen()
63
X = int(2 * RR(N) ** (1 / 2) / e) # Equivalent to 2^(n / 2 + 1) / e
64
for _, k in _bdf_theorem_6(N, e, d_bit_length, d1, d1_bit_length):
65
f = k * p ** 2 - (1 + k * (N + 1)) * p + k * N
66
for p0 in f.roots(multiplicities=False):
67
f = x * e + int(p0)
68
for p_, q_, d_ in _bdf_corollary_1(e, f, N, m, t, X):
69
return p_, q_, d_
70
71
return None
72
73
74
def _bdf_4_2(N, e, d_bit_length, d1, d1_bit_length):
75
for d0, k in _bdf_theorem_6(N, e, d_bit_length, d1, d1_bit_length):
76
if gcd(e, k) != 1:
77
continue
78
79
d1 = pow(e, -1, k)
80
for v in range(ceil(e / k) + 1):
81
d2 = int(QQ(d0) / k + v - QQ(d1) / k)
82
d = k * d2 + d1
83
if pow(pow(2, e, N), d, N) == 2:
84
phi = (e * d - 1) // k
85
factors = known_phi.factorize(N, phi)
86
if factors:
87
return *factors, d
88
89
return None
90
91
92
def _bdf_4_3(N, e, d_bit_length, d0, d0_bit_length, d1, d1_bit_length, r, m, t):
93
logging.info(f"Trying {m = }, {t = }...")
94
p = ZZ["p"].gen()
95
x = Zmod(N)["x"].gen()
96
X = int(2 * RR(N) ** (1 / 2) / r) # Equivalent to 2^(n / 2 + 1) / r
97
for _, k in _bdf_theorem_6(N, e, d_bit_length, d1, d1_bit_length):
98
f = k * p ** 2 + (e * d0 - (1 + k * (N + 1))) * p + k * N
99
for p0 in hensel_roots(f, 2, d0_bit_length):
100
f = x * r + p0
101
for p_, q_, d_ in _bdf_corollary_1(e, f, N, m, t, X):
102
return p_, q_, d_
103
104
return None
105
106
107
def _bm_4(N, e, d_bit_length, d1, d1_bit_length, m, t):
108
d_ = d1 << (d_bit_length - d1_bit_length)
109
k_ = (e * d_ - 1) // (N + 1)
110
111
x, y, z = ZZ["x", "y", "z"].gens()
112
f = e * x + (k_ + y) * z + e * d_ - 1
113
X = 2 ** (d_bit_length - d1_bit_length) # Equivalent to N^delta
114
Y = int(4 * e / RR(N) ** (1 / 2)) # Equivalent to 4N^(alpha - 1 / 2)
115
Z = int(3 * RR(N) ** (1 / 2))
116
logging.info(f"Trying {m = }, {t = }...")
117
for x0, y0, z0 in blomer_may.modular_trivariate(f, N, m, t, X, Y, Z):
118
d = d_ + x0
119
phi = N - z0
120
if pow(pow(2, e, N), d, N) == 2:
121
factors = known_phi.factorize(N, phi)
122
if factors:
123
return *factors, d
124
125
return None
126
127
128
def _bm_6(N, e, d_bit_length, d0, d0_bit_length, M, m, t):
129
y, z = ZZ["y", "z"].gens()
130
f = y * (N - z) - e * d0 + 1
131
Y = e # Equivalent to N^alpha
132
Z = int(3 * RR(N) ** (1 / 2))
133
logging.info(f"Trying {m = }, {t = }...")
134
for y0, z0 in blomer_may.modular_bivariate(f, e * M, m, t, Y, Z):
135
phi = N - z0
136
d = pow(e, -1, phi)
137
if pow(pow(2, e, N), d, N) == 2:
138
factors = known_phi.factorize(N, phi)
139
if factors:
140
return *factors, d
141
142
return None
143
144
145
def _ernst_4_1_1(N, e, d_bit_length, d1, d1_bit_length, m, t):
146
d_ = d1 << (d_bit_length - d1_bit_length)
147
R = e * d_ - 1
148
149
x, y, z = ZZ["x", "y", "z"].gens()
150
f = e * x - N * y + y * z + R
151
X = 2 ** (d_bit_length - d1_bit_length) # Equivalent to N^delta
152
Y = 2 ** d_bit_length # Equivalent to N^beta
153
Z = int(3 * RR(N) ** (1 / 2))
154
W = N * Y
155
logging.info(f"Trying {m = }, {t = }...")
156
for x0, y0, z0 in ernst.integer_trivariate_1(f, m, t, W, X, Y, Z):
157
d = d_ + x0
158
phi = N - z0
159
if pow(pow(2, e, N), d, N) == 2:
160
factors = known_phi.factorize(N, phi)
161
if factors:
162
return *factors, d
163
164
return None
165
166
167
def _ernst_4_1_2(N, e, d_bit_length, d1, d1_bit_length, m, t):
168
d_ = d1 << (d_bit_length - d1_bit_length)
169
k_ = (e * d_ - 1) // N
170
R = e * d_ - 1 - k_ * N
171
172
x, y, z = ZZ["x", "y", "z"].gens()
173
f = e * x - N * y + y * z + k_ * z + R
174
X = 2 ** (d_bit_length - d1_bit_length) # Equivalent to N^delta
175
Y = 4 * int(max(2 ** (d_bit_length - d1_bit_length), 2 ** d_bit_length / RR(N) ** (1 / 2))) # Equivalent to 4N^max(delta, beta - 1 / 2)
176
Z = int(3 * RR(N) ** (1 / 2))
177
W = N * Y
178
logging.info(f"Trying {m = }, {t = }...")
179
for x0, y0, z0 in ernst.integer_trivariate_2(f, m, t, W, X, Y, Z):
180
d = d_ + x0
181
phi = N - z0
182
if pow(pow(2, e, N), d, N) == 2:
183
factors = known_phi.factorize(N, phi)
184
if factors:
185
return *factors, d
186
187
return None
188
189
190
def _ernst_4_2(N, e, d_bit_length, d1, d1_bit_length, m, t):
191
d_ = d1 << (d_bit_length - d1_bit_length)
192
k_ = (e * d_ - 1) // N
193
R = e * d_ - 1 - k_ * N
194
195
x, y, z = ZZ["x", "y", "z"].gens()
196
f = e * x - N * y + y * z + k_ * z + R
197
X = 2 ** (d_bit_length - d1_bit_length) # Equivalent to N^delta
198
Y = 4 * int(max((e * 2 ** (d_bit_length - d1_bit_length)) / N, e / RR(N) ** (1 / 2))) # Equivalent to 4N^max(alpha + delta - 1, alpha - 1 / 2)
199
Z = int(3 * RR(N) ** (1 / 2))
200
W = N * Y
201
logging.info(f"Trying {m = }, {t = }...")
202
for x0, y0, z0 in ernst.integer_trivariate_2(f, m, t, W, X, Y, Z):
203
d = d_ + x0
204
phi = N - z0
205
if pow(pow(2, e, N), d, N) == 2:
206
factors = known_phi.factorize(N, phi)
207
if factors:
208
return *factors, d
209
210
return None
211
212
213
def _ernst_4_3(N, e, d_bit_length, d0, d0_bit_length, M, m, t):
214
R = e * d0 - 1
215
216
x, y, z = ZZ["x", "y", "z"].gens()
217
f = e * M * x - N * y + y * z + R
218
X = 2 ** (d_bit_length - d0_bit_length) # Equivalent to N^delta
219
Y = 2 ** d_bit_length # Equivalent to N^beta
220
Z = int(3 * RR(N) ** (1 / 2))
221
W = N * Y
222
logging.info(f"Trying {m = }, {t = }...")
223
for x0, y0, z0 in ernst.integer_trivariate_1(f, m, t, W, X, Y, Z):
224
d = x0 * M + d0
225
phi = N - z0
226
if pow(pow(2, e, N), d, N) == 2:
227
factors = known_phi.factorize(N, phi)
228
if factors:
229
return *factors, d
230
231
return None
232
233
234
def attack(N, e, partial_d, factor_e=True, m=1, t=None):
235
"""
236
Recovers the prime factors of a modulus and the private exponent if part of the private exponent is known.
237
More information: Boneh D., Durfee G., Frankel Y., "An Attack on RSA Given a Small Fraction of the Private Key Bits"
238
More information: Blomer J., May A., "New Partial Key Exposure Attacks on RSA"
239
More information: Ernst M. et al., "Partial Key Exposure Attacks on RSA Up to Full Size Exponents"
240
:param N: the modulus
241
:param e: the public exponent
242
:param partial_d: the partial private exponent d (PartialInteger)
243
:param factor_e: whether we should attempt to factor e (for BDF) if it is not prime (default: True)
244
:param m: the m value to use for the small roots method (default: 1)
245
:param t: the t value to use for the small roots method (default: automatically computed using m)
246
:return: a tuple containing the prime factors and the private exponent, or None if the private exponent was not found
247
"""
248
d_bit_length = partial_d.bit_length
249
d0, d0_bit_length = partial_d.get_known_lsb()
250
d1, d1_bit_length = partial_d.get_known_msb()
251
assert d0_bit_length > 0 or d1_bit_length > 0, "At least some lsb or msb of d must be known."
252
253
n = N.bit_length()
254
# Subtract one here, because 2^t < e < 2^(t + 1).
255
t_ = e.bit_length() - 1
256
alpha = t_ / n
257
beta = d_bit_length / n
258
assert beta >= 0.25, "Use Wiener's or the Boneh-Durfee attack if d is very small."
259
260
if d0_bit_length > 0 and d1_bit_length > 0:
261
# Known lsbs and msbs.
262
M = 2 ** d0_bit_length
263
264
if 1 <= t_ <= n / 2 and d0_bit_length >= n / 4 and d1_bit_length >= t_:
265
logging.info("Using Boneh-Durfee-Frankel (Section 4.3)...")
266
assert t is not None, "t can not be None for Boneh-Durfee-Frankel small roots."
267
return _bdf_4_3(N, e, d_bit_length, d0, d0_bit_length, d1, d1_bit_length, M, m, t)
268
269
logging.info("No attacks were found to fit the provided parameters (known lsbs and msbs).")
270
return None
271
272
if d0_bit_length > 0:
273
# Known lsbs.
274
M = 2 ** d0_bit_length
275
delta = (d_bit_length - d0_bit_length) / n
276
277
if e < RR(N) ** (7 / 8) and RR(N) ** (1 / 6 + 1 / 3 * sqrt(1 + 6 * alpha)) <= M:
278
logging.info("Using Blomer-May (Section 6)...")
279
t = int((2 / 3 * (1 - delta - alpha) / (2 * alpha - 1))) if t is None else t
280
return _bm_6(N, e, d_bit_length, d0, d0_bit_length, M, m, t)
281
282
if delta <= 5 / 6 - 1 / 3 * sqrt(1 + 6 * beta):
283
logging.info("Using Ernst (Section 4.3)...")
284
t = int((1 / 2 - delta) * m) if t is None else t
285
return _ernst_4_3(N, e, d_bit_length, d0, d0_bit_length, M, m, t)
286
287
# Last resort method: enumerate possible k values (very slow if e is too large).
288
if d0_bit_length >= n / 4:
289
logging.info("Using Boneh-Durfee-Frankel (Section 3)...")
290
assert t is not None, "t can not be None for Boneh-Durfee-Frankel small roots."
291
return _bdf_3(N, e, d_bit_length, d0, d0_bit_length, M, m, t)
292
293
logging.info("No attacks were found to fit the provided parameters (known lsbs).")
294
return None
295
296
if d1_bit_length > 0:
297
delta = (d_bit_length - d1_bit_length) / n
298
299
if n / 4 <= t_ <= n / 2 and d1_bit_length >= t_ and (is_prime(e) or factor_e):
300
logging.info("Using Boneh-Durfee-Frankel (Section 4.1)...")
301
assert t is not None, "t can not be None for Boneh-Durfee-Frankel small roots."
302
return _bdf_4_1(N, e, d_bit_length, d1, d1_bit_length, m, t)
303
304
if 0 <= t_ <= n / 2 and d1_bit_length >= n - t_:
305
logging.info("Using Boneh-Durfee-Frankel (Section 4.2)...")
306
return _bdf_4_2(N, e, d_bit_length, d1, d1_bit_length)
307
308
# Blomer-May Section 4 is superseded by Ernst Section 4.2.
309
# if 1 / 2 < alpha <= (sqrt(6) - 1) / 2 and delta <= 1 / 8 * (5 - 2 * alpha - sqrt(36 * alpha ** 2 + 12 * alpha - 15)):
310
# logging.info("Using Blomer-May (Section 4)...")
311
# t = int((2 / 3 * (1 - delta - alpha) / (2 * alpha - 1))) if t is None else t
312
# return _bm_4(N, e, d_bit_length, d1, d1_bit_length, m, t)
313
314
margin4_1_1 = 5 / 6 - 1 / 3 * sqrt(1 + 6 * beta) - delta
315
margin4_1_2 = (3 / 16 - delta) if beta <= 11 / 16 else (1 / 3 + 1 / 3 * beta - 1 / 3 * sqrt(4 * beta ** 2 + 2 * beta - 2) - delta)
316
if margin4_1_1 > max(0, margin4_1_2):
317
logging.info("Using Ernst (Section 4.1.1)...")
318
t = int((1 / 2 - delta) * m) if t is None else t
319
return _ernst_4_1_1(N, e, d_bit_length, d1, d1_bit_length, m, t)
320
321
if margin4_1_2 > max(0, margin4_1_1):
322
logging.info("Using Ernst (Section 4.1.2)...")
323
gamma = max(delta, beta - 1 / 2)
324
t = int(((1 / 2 - delta - gamma) / (2 * gamma)) * m) if t is None else t
325
return _ernst_4_1_2(N, e, d_bit_length, d1, d1_bit_length, m, t)
326
327
if alpha > 1 / 2 and delta <= 1 / 3 + 1 / 3 * alpha - 1 / 3 * sqrt(4 * alpha ** 2 + 2 * alpha - 2):
328
logging.info("Using Ernst (Section 4.2)...")
329
gamma = max(alpha + delta - 1, alpha - 1 / 2)
330
t = int(((1 / 2 - delta - gamma) / (2 * gamma)) * m) if t is None else t
331
return _ernst_4_2(N, e, d_bit_length, d1, d1_bit_length, m, t)
332
333
logging.info("No attacks were found to fit the provided parameters (known msbs).")
334
return None
335
336
logging.info("No attacks were found to fit the provided parameters.")
337
return None
338
339