Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/attacks/factorization/coppersmith.py
2589 views
1
import logging
2
import os
3
import sys
4
from math import ceil
5
from math import log
6
from math import pi
7
from math import sqrt
8
9
from sage.all import ZZ
10
from sage.all import Zmod
11
12
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))
13
if sys.path[1] != path:
14
sys.path.insert(1, path)
15
16
from shared.small_roots import coron_direct
17
from shared.small_roots import herrmann_may_multivariate
18
from shared.small_roots import howgrave_graham
19
20
21
def factorize_p(N, partial_p, beta=0.5, epsilon=0.125, m=None, t=None):
22
"""
23
Recover the prime factors from a modulus using Coppersmith's method and bits of one prime factor p are known.
24
More information: May A., "New RSA Vulnerabilities Using Lattice Reduction Methods" (Section 3.2)
25
More information: Herrmann M., May A., "Solving Linear Equations Modulo Divisors: On Factoring Given Any Bits" (Section 3 and 4)
26
:param N: the modulus
27
:param partial_p: the partial prime factor p (PartialInteger)
28
:param beta: the parameter beta (default: 0.5)
29
:param epsilon: the parameter epsilon (default: 0.125)
30
:param m: the number of normal shifts to use (default: automatically computed using beta and epsilon)
31
:param t: the number of additional shifts to use (default: automatically computed using beta and epsilon)
32
:return: a tuple containing the prime factors, or None if the factors could not be found
33
"""
34
n = partial_p.unknowns
35
assert n > 0
36
if n == 1:
37
m = ceil(max(beta ** 2 / epsilon, 7 * beta)) if m is None else m
38
t = int((1 / beta - 1) * m) if t is None else t
39
small_roots = howgrave_graham.modular_univariate
40
elif n == 2:
41
m = ceil((3 * beta * (1 + sqrt(1 - beta))) / epsilon) if m is None else m
42
t = int((1 - sqrt(1 - beta)) * m) if t is None else t
43
small_roots = herrmann_may_multivariate.modular_multivariate
44
else:
45
m = ceil((n * (1 / pi * (1 - beta) ** (-0.278465) - beta * log(1 - beta))) / epsilon) if m is None else m
46
t = int((1 - (1 - beta) ** (1 / n)) * m) if t is None else t
47
small_roots = herrmann_may_multivariate.modular_multivariate
48
49
x = Zmod(N)[tuple(f"x{i}" for i in range(n))].gens()
50
f = partial_p.sub(x)
51
X = partial_p.get_unknown_bounds()
52
logging.info(f"Trying {m = }, {t = }...")
53
for roots in small_roots(f, N, m, t, X):
54
p = partial_p.sub(roots)
55
if 1 < p < N and N % p == 0:
56
return p, N // p
57
58
return None
59
60
61
def factorize_pq(N, partial_p, partial_q, k=None):
62
"""
63
Recover the prime factors from a modulus using Coppersmith's method and bits of both prime factors p and q are known.
64
:param N: the modulus
65
:param partial_p: the partial prime factor p (PartialInteger)
66
:param partial_q: the partial prime factor q (PartialInteger)
67
:param k: the number of shifts to use for Coron's method, must be set if the total number of unknown components is two (default: None)
68
:return: a tuple containing the prime factors, or None if the factors could not be found
69
"""
70
np = partial_p.unknowns
71
nq = partial_q.unknowns
72
assert np > 0 and nq > 0
73
74
x = ZZ[tuple(f"x{i}" for i in range(np + nq))].gens()
75
f = partial_p.sub(x[:np]) * partial_q.sub(x[np:]) - N
76
Xp = partial_p.get_unknown_bounds()
77
Xq = partial_q.get_unknown_bounds()
78
79
if np == 1 and nq == 1:
80
assert k is not None, "k must be set if the total number of unknown components is two."
81
logging.info(f"Trying {k = }...")
82
for x0, x1 in coron_direct.integer_bivariate(f, k, Xp[0], Xq[0]):
83
p = partial_p.sub([x0])
84
q = partial_q.sub([x1])
85
if p * q == N:
86
return p, q
87
else:
88
# TODO: Jochemsz-May multivariate integer roots?
89
# Or "Factoring RSA Modulus with Known Bits from Both p and q: A Lattice Method"?
90
pass
91
92
return None
93
94