Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/attacks/rsa/known_crt_exponents.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 log
7
8
from sage.all import Zmod
9
from sage.all import is_prime
10
11
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))
12
if sys.path[1] != path:
13
sys.path.insert(1, path)
14
15
from shared import ceil_div
16
from shared.small_roots import herrmann_may
17
from shared.small_roots import howgrave_graham
18
19
20
def _get_possible_primes(e, d):
21
logging.debug(f"Looking for possible primes for {e = }, {d = }")
22
mul = e * d - 1
23
for k in range(3, e):
24
if mul % k == 0:
25
p = (mul // k) + 1
26
if is_prime(p):
27
yield p
28
29
30
def attack(e_start, e_end, N=None, dp=None, dq=None, p_bit_length=None, q_bit_length=None):
31
"""
32
Generates possible prime factors for a modulus, if d_p and/or d_q are known.
33
More information: Campagna M., Sethi A., "Key Recovery Method for CRT Implementation of RSA"
34
:param e_start: the start value of the public exponent (inclusive)
35
:param e_end: the end value of the public exponent (exclusive)
36
:param N: the modulus, will be used to check the factors if not None (default: None)
37
:param dp: the d exponent for p, will be used to generate possible factors for p if not None (default: None)
38
:param dq: the d exponent for q, will be used to generate possible factors for q if not None (default: None)
39
:param p_bit_length: the bit length of p, will be used to check possible factors for p if not None (default: None)
40
:param q_bit_length: the bit length of q, will be used to check possible factors for q if not None (default: None)
41
:return: a generator generating tuples containing possible prime factors
42
"""
43
assert not (dp is None and dq is None), "At least one of the CRT private exponents should be known."
44
45
if dp is not None and dq is not None:
46
for e in range(e_start, e_end, 2):
47
for p in _get_possible_primes(e, dp):
48
for q in _get_possible_primes(e, dq):
49
if (N is None or p * q == N) and (p_bit_length is None or p.bit_length() == p_bit_length) and (q_bit_length is None or q.bit_length() == q_bit_length):
50
yield p, q
51
52
return None
53
54
if dp is not None:
55
for e in range(e_start, e_end, 2):
56
for p in _get_possible_primes(e, dp):
57
if p_bit_length is None or p.bit_length() == p_bit_length:
58
if N is None:
59
yield p
60
elif N % p == 0:
61
yield p, N // p
62
63
return None
64
65
if dq is not None:
66
for e in range(e_start, e_end, 2):
67
for q in _get_possible_primes(e, dq):
68
if q_bit_length is None or q.bit_length() == q_bit_length:
69
if N is None:
70
yield q
71
elif N % q == 0:
72
yield q, N // q
73
74
return None
75
76
77
def _factor_msb(N, e, dpM, dp_unknown_lsb, k, m, t):
78
logging.info(f"Trying {k = }")
79
g = gcd(e, k * N)
80
x = Zmod(k * N)["x"].gen()
81
f = x + (e * dpM * 2 ** dp_unknown_lsb + k - 1) * pow(e, -1, k // g * N)
82
X = 2 ** dp_unknown_lsb
83
logging.info(f"Trying {m = }, {t = }...")
84
for x0, in howgrave_graham.modular_univariate(f, k * N, m, t, X):
85
dp = int(f(x0))
86
p = gcd(dp, N)
87
if N % p == 0:
88
return p, N // p
89
90
91
def _factor_lsb(N, e, dpL, dpL_bit_length, dp_unknown_msb, k, m, t):
92
logging.info(f"Trying {k = }")
93
g = gcd(2 ** dpL_bit_length * e, k * N)
94
x = Zmod(k * N)["x"].gen()
95
f = x + (e * dpL + k - 1) * pow(2 ** dpL_bit_length * e, -1, k // g * N)
96
X = 2 ** dp_unknown_msb
97
logging.info(f"Trying {m = }, {t = }...")
98
for x0, in howgrave_graham.modular_univariate(f, k * N, m, t, X):
99
dp = int(f(x0))
100
p = gcd(dp, N)
101
if N % p == 0:
102
return p, N // p
103
104
105
def attack_partial(N, e, partial_dp, partial_dq, m=None, t=None, check_bounds=True):
106
"""
107
Recovers the prime factors from a modulus if the most or least significant bits of dp and dq are known.
108
More information: Alexander M., Julian N., Santanu S., "Approximate Divisor Multiples - Factoring with Only a Third of the Secret CRT-Exponents"
109
:param N: the modulus
110
:param e: the exponent
111
:param partial_dp: d mod (p - 1) (PartialInteger)
112
:param partial_dq: d mod (q - 1) (PartialInteger)
113
:param m: the parameter m for small roots (default: automatically calculated using beta = 0.5 and epsilon = 0.125)
114
:param t: the parameter t for small roots (default: automatically calculated using beta = 0.5 and epsilon = 0.125)
115
:param check_bounds: perform bounds check (default: True)
116
:return: a tuple containing the prime factors, or None if the factors were not found
117
"""
118
alpha = log(e, N)
119
dp_bit_length = partial_dp.bit_length
120
dq_bit_length = partial_dq.bit_length
121
assert dp_bit_length == dq_bit_length, "dp and dq should be of equal bit length."
122
123
beta = 0.5
124
epsilon = 0.125
125
m = ceil(max(beta ** 2 / epsilon, 7 * beta)) if m is None else m
126
t = int((1 / beta - 1) * m) if t is None else t
127
128
dpM, dpM_bit_length = partial_dp.get_known_msb()
129
dqM, dqM_bit_length = partial_dq.get_known_msb()
130
if dpM_bit_length > 0 and dqM_bit_length > 0:
131
# Section 3.1.
132
dp_unknown_lsb = partial_dp.get_unknown_lsb()
133
dq_unknown_lsb = partial_dq.get_unknown_lsb()
134
delta = log(max(2 ** dp_unknown_lsb, 2 ** dq_unknown_lsb), N)
135
assert not check_bounds or delta < min(1 / 4 + alpha, 1 / 2 - 2 * alpha), f"Bounds check failed ({delta} < {min(1 / 4 + alpha, 1 / 2 - 2 * alpha)})."
136
137
x = Zmod(e)["x"].gen()
138
A_ = ceil_div(2 ** (dp_unknown_lsb + dq_unknown_lsb) * e ** 2 * dpM * dqM, N)
139
140
# First case.
141
f = x ** 2 - (1 - A_ * (N - 1)) * x + A_
142
for k, _ in f.roots():
143
if k == 0:
144
continue
145
146
factors = _factor_msb(N, e, dpM, dp_unknown_lsb, int(k), m, t)
147
if factors:
148
return factors
149
factors = _factor_msb(N, e, dqM, dq_unknown_lsb, int(k), m, t)
150
if factors:
151
return factors
152
153
# Second case.
154
f = x ** 2 + (1 - A_ * (N - 1) + e) * x + A_
155
for k, _ in f.roots():
156
if k == 0:
157
continue
158
159
factors = _factor_msb(N, e, dpM, dp_unknown_lsb, int(k), m, t)
160
if factors:
161
return factors
162
factors = _factor_msb(N, e, dqM, dq_unknown_lsb, int(k), m, t)
163
if factors:
164
return factors
165
166
dpL, dpL_bit_length = partial_dp.get_known_lsb()
167
dqL, dqL_bit_length = partial_dq.get_known_lsb()
168
if dpL_bit_length > 0 and dqL_bit_length > 0:
169
# Section 3.2.
170
dp_unknown_msb = partial_dp.get_unknown_msb()
171
dq_unknown_msb = partial_dq.get_unknown_msb()
172
assert dpL_bit_length == dqL_bit_length, "dp and dq LSB should be of equal bit length."
173
delta = log(max(2 ** dp_unknown_msb, 2 ** dq_unknown_msb), N)
174
assert not check_bounds or delta < min(1 / 4 + alpha, 1 / 2 - 2 * alpha), f"Bounds check failed ({delta} < {min(1 / 4 + alpha, 1 / 2 - 2 * alpha)})."
175
176
i = dpL_bit_length
177
pr = Zmod(2 ** i * e)["x, y"]
178
x, y = pr.gens()
179
A = -e ** 2 * dpL * dqL + e * dpL + e * dqL - 1
180
f = (N - 1) * x * y - (e * dqL - 1) * x - (e * dpL - 1) * y + A
181
g = f * pow((N - 1) // gcd(N - 1, e * 2 ** i), -1, 2 ** i * e)
182
for k, l in herrmann_may.modular_bivariate(g, 2 ** i * e, 2, 2, e, e):
183
if k == 0 or l == 0:
184
continue
185
186
factors = _factor_lsb(N, e, dpL, dpL_bit_length, dp_unknown_msb, int(k), m, t)
187
if factors:
188
return factors
189
factors = _factor_lsb(N, e, dqL, dqL_bit_length, dq_unknown_msb, int(l), m, t)
190
if factors:
191
return factors
192
193
return None
194
195