forked from jvdsn/crypto-attacks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
wiener_attack_lattice.py
152 lines (128 loc) · 4.68 KB
/
wiener_attack_lattice.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
import logging
import os
import sys
from itertools import combinations
from math import isqrt
from math import prod
from sage.all import RR
from sage.all import ZZ
from sage.all import matrix
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))
if sys.path[1] != path:
sys.path.insert(1, path)
from attacks.factorization import known_phi
from shared.lattice import shortest_vectors
from shared.small_roots import aono
from shared.small_roots import reduce_lattice
def attack(N, e):
"""
Recovers the prime factors of a modulus and the private exponent if the private exponent is too small.
More information: Nguyen P. Q., "Public-Key Cryptanalysis"
:param N: the modulus
:param e: the public exponent
:return: a tuple containing the prime factors and the private exponent, or None if the private exponent was not found
"""
s = isqrt(N)
L = matrix(ZZ, [[e, s], [N, 0]])
for v in shortest_vectors(L):
d = v[1] // s
k = abs(v[0] - e * d) // N
d = abs(d)
if pow(pow(2, e, N), d, N) != 2:
continue
phi = (e * d - 1) // k
factors = known_phi.factorize(N, phi)
if factors:
return *factors, int(d)
return None
# Construct R_{u, v} for a specific monomial.
def _construct_relation(N, monomial, x):
vars = monomial.variables()
l = [x[i] for i in range(x.index(vars[-1]) + 1)]
R = 1
u = 0
v = 0
i = len(vars)
for var in vars:
if var != x[0] and var < l[0] and len(l) >= 2 * i:
# Guo equation
R *= l[0] - var
l.pop(0)
v += 1
else:
# Wiener equation
R *= var - N
u += 1
l.remove(var)
i -= 1
return R, u, v
def attack_multiple_exponents_1(N, e, alpha):
"""
Recovers the prime factors of a modulus given multiple public exponents with small corresponding private exponents.
More information: Howgrave-Graham N., Seifert J., "Extending Wiener’s Attack in the Presence of Many Decrypting Exponents"
:param N: the modulus
:param e: the public exponent
:param alpha: the bound on the private exponents (i.e. d < N^alpha)
:return: a tuple containing the prime factors, or None if the prime factors were not found
"""
n = len(e)
pr = ZZ[",".join(f"x{i}" for i in range(n))]
x = pr.gens()
monomials = [1]
for i, xi in enumerate(x):
monomials.append(xi)
for j in range(i):
for comb in combinations(x[:i], j + 1):
monomials.append(prod(comb) * xi)
L = matrix(ZZ, len(monomials))
exp_a = [n]
exp_b = [0]
for col, monomial in enumerate(monomials):
if col == 0:
L[0, 0] = 1
continue
R, u, v = _construct_relation(N, monomial, x)
for row, monomial in enumerate(monomials):
if row == 0:
L[0, col] = R.constant_coefficient()
else:
L[row, col] = R.monomial_coefficient(monomial) * monomial(*e)
exp_a.append(n - v)
exp_b.append(u / 2)
max_a = max(exp_a)
max_b = max(exp_b)
D = matrix(ZZ, len(monomials))
for i, (a, b) in enumerate(zip(exp_a, exp_b)):
D[i, i] = int(RR(N) ** ((max_a - a) * alpha + (max_b - b)))
L = L * D
L_ = reduce_lattice(L)
b = L.solve_left(L_[0])
phi = round(b[1] / b[0] * e[0])
return known_phi.factorize(N, phi)
def attack_multiple_exponents_2(N, e, d_bit_length, m=1):
"""
Recovers the prime factors of a modulus given multiple public exponents with small corresponding private exponents.
More information: Aono Y., "Minkowski sum based lattice construction for multivariate simultaneous Coppersmith’s technique and applications to RSA" (Section 4)
:param N: the modulus
:param e: the public exponent
:param d_bit_length: the bit length of the private exponents
:param m: the m value to use for the small roots method (default: 1)
:return: a tuple containing the prime factors, or None if the prime factors were not found
"""
l = len(e)
assert len(set(e)) == l, "All public exponents must be distinct"
assert l >= 1, "At least one public exponent is required."
pr = ZZ[",".join(f"x{i}" for i in range(l)) + ",y"]
gens = pr.gens()
x = gens[:-1]
y = gens[-1]
F = [-1 + x[k] * (y + N) for k in range(l)]
X = [2 ** d_bit_length for _ in range(l)]
Y = 3 * isqrt(N)
logging.info(f"Trying {m = }...")
for roots in aono.integer_multivariate(F, e, m, X + [Y]):
phi = roots[y] + N
factors = known_phi.factorize(N, phi)
if factors:
return factors
return None