Created
November 22, 2020 08:26
-
-
Save maojui/fa9b5dfd37460bfacff65a7e65eaa177 to your computer and use it in GitHub Desktop.
Boneh and Durfee Attack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sage | |
# This code is taken from https://github.com/mimoo/RSA-and-LLL-attacks | |
import time | |
############################################ | |
# Config | |
########################################## | |
""" | |
Setting debug to true will display more informations | |
about the lattice, the bounds, the vectors... | |
""" | |
debug = True | |
""" | |
Setting strict to true will stop the algorithm (and | |
return (-1, -1)) if we don't have a correct | |
upperbound on the determinant. Note that this | |
doesn't necesseraly mean that no solutions | |
will be found since the theoretical upperbound is | |
usualy far away from actual results. That is why | |
you should probably use `strict = False` | |
""" | |
strict = False | |
""" | |
This is experimental, but has provided remarkable results | |
so far. It tries to reduce the lattice as much as it can | |
while keeping its efficiency. I see no reason not to use | |
this option, but if things don't work, you should try | |
disabling it | |
""" | |
helpful_only = True | |
dimension_min = 7 # stop removing if lattice reaches that dimension | |
############################################ | |
# Functions | |
########################################## | |
def helpful_vectors(BB, modulus): | |
'display stats on helpful vectors' | |
nothelpful = 0 | |
for ii in range(BB.dimensions()[0]): | |
if BB[ii,ii] >= modulus: | |
nothelpful += 1 | |
print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful") | |
def matrix_overview(BB, bound): | |
'display matrix picture with 0 and X' | |
for ii in range(BB.dimensions()[0]): | |
a = ('%02d ' % ii) | |
for jj in range(BB.dimensions()[1]): | |
a += '0' if BB[ii,jj] == 0 else 'X' | |
if BB.dimensions()[0] < 60: | |
a += ' ' | |
if BB[ii, ii] >= bound: | |
a += '~' | |
print(a) | |
def remove_unhelpful(BB, monomials, bound, current): | |
''' | |
tries to remove unhelpful vectors | |
we start at current = n-1 (last vector) | |
end of our recursive function | |
''' | |
if current == -1 or BB.dimensions()[0] <= dimension_min: | |
return BB | |
# we start by checking from the end | |
for ii in range(current, -1, -1): | |
# if it is unhelpful: | |
if BB[ii, ii] >= bound: | |
affected_vectors = 0 | |
affected_vector_index = 0 | |
# let's check if it affects other vectors | |
for jj in range(ii + 1, BB.dimensions()[0]): | |
# if another vector is affected: | |
# we increase the count | |
if BB[jj, ii] != 0: | |
affected_vectors += 1 | |
affected_vector_index = jj | |
# level:0 | |
# if no other vectors end up affected | |
# we remove it | |
if affected_vectors == 0: | |
print("* removing unhelpful vector", ii) | |
BB = BB.delete_columns([ii]) | |
BB = BB.delete_rows([ii]) | |
monomials.pop(ii) | |
BB = remove_unhelpful(BB, monomials, bound, ii-1) | |
return BB | |
# level:1 | |
# if just one was affected we check | |
# if it is affecting someone else | |
elif affected_vectors == 1: | |
affected_deeper = True | |
for kk in range(affected_vector_index + 1, BB.dimensions()[0]): | |
# if it is affecting even one vector | |
# we give up on this one | |
if BB[kk, affected_vector_index] != 0: | |
affected_deeper = False | |
# remove both it if no other vector was affected and | |
# this helpful vector is not helpful enough | |
# compared to our unhelpful one | |
if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]): | |
print("* removing unhelpful vectors", ii, "and", affected_vector_index) | |
BB = BB.delete_columns([affected_vector_index, ii]) | |
BB = BB.delete_rows([affected_vector_index, ii]) | |
monomials.pop(affected_vector_index) | |
monomials.pop(ii) | |
BB = remove_unhelpful(BB, monomials, bound, ii-1) | |
return BB | |
# nothing happened | |
return BB | |
""" | |
Returns: | |
* 0,0 if it fails | |
* -1,-1 if `strict=true`, and determinant doesn't bound | |
* x0,y0 the solutions of `pol` | |
""" | |
def boneh_durfee_algorithm(pol, modulus, mm, tt, XX, YY): | |
""" | |
Boneh and Durfee revisited by Herrmann and May | |
finds a solution if: | |
* d < N^delta | |
* |x| < e^delta | |
* |y| < e^0.5 | |
whenever delta < 1 - sqrt(2)/2 ~ 0.292 | |
""" | |
# substitution (Herrman and May) | |
PR.<u, x, y> = PolynomialRing(ZZ) | |
Q = PR.quotient(x*y + 1 - u) # u = xy + 1 | |
polZ = Q(pol).lift() | |
UU = XX*YY + 1 | |
# x-shifts | |
gg = [] | |
for kk in range(mm + 1): | |
for ii in range(mm - kk + 1): | |
xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk | |
gg.append(xshift) | |
gg.sort() | |
# x-shifts list of monomials | |
monomials = [] | |
for polynomial in gg: | |
for monomial in polynomial.monomials(): | |
if monomial not in monomials: | |
monomials.append(monomial) | |
monomials.sort() | |
# y-shifts (selected by Herrman and May) | |
for jj in range(1, tt + 1): | |
for kk in range(floor(mm/tt) * jj, mm + 1): | |
yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk) | |
yshift = Q(yshift).lift() | |
gg.append(yshift) # substitution | |
# y-shifts list of monomials | |
for jj in range(1, tt + 1): | |
for kk in range(floor(mm/tt) * jj, mm + 1): | |
monomials.append(u^kk * y^jj) | |
# construct lattice B | |
nn = len(monomials) | |
BB = Matrix(ZZ, nn) | |
for ii in range(nn): | |
BB[ii, 0] = gg[ii](0, 0, 0) | |
for jj in range(1, ii + 1): | |
if monomials[jj] in gg[ii].monomials(): | |
BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY) | |
# Prototype to reduce the lattice | |
if helpful_only: | |
# automatically remove | |
BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1) | |
# reset dimension | |
nn = BB.dimensions()[0] | |
if nn == 0: | |
# print("failure") | |
return 0,0 | |
# check if vectors are helpful | |
if debug: | |
helpful_vectors(BB, modulus^mm) | |
# check if determinant is correctly bounded | |
det = BB.det() | |
bound = modulus^(mm*nn) | |
if det >= bound: | |
# print("We do not have det < bound. Solutions might not be found.") | |
# print("Try with highers m and t.") | |
if debug: | |
diff = (log(det) - log(bound)) / log(2) | |
print("size det(L) - size e^(m*n) = ", floor(diff)) | |
if strict: | |
return -1, -1 | |
# else: | |
# print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)") | |
# display the lattice basis | |
if debug: | |
matrix_overview(BB, modulus^mm) | |
# LLL | |
BB = BB.LLL() | |
# transform vector 1 & 2 -> polynomials 1 & 2 | |
PR.<w,z> = PolynomialRing(ZZ) | |
pol1 = pol2 = 0 | |
for jj in range(nn): | |
pol1 += monomials[jj](w*z+1,w,z) * BB[0, jj] / monomials[jj](UU,XX,YY) | |
pol2 += monomials[jj](w*z+1,w,z) * BB[1, jj] / monomials[jj](UU,XX,YY) | |
# resultant | |
PR.<q> = PolynomialRing(ZZ) | |
rr = pol1.resultant(pol2) | |
if rr.is_zero() or rr.monomials() == [1]: | |
# print("the two first vectors are not independant") | |
return 0, 0 | |
rr = rr(q, q) | |
# solutions | |
soly = rr.roots() | |
if len(soly) == 0: | |
# print("Your prediction (delta) is too small") | |
return 0, 0 | |
soly = soly[0][0] | |
ss = pol1(q, soly) | |
solx = ss.roots()[0][0] | |
# | |
return solx, soly | |
def boneh_durfee(N, e, delta=.26, m=4): | |
''' | |
@N : modulus | |
@e : exponent | |
@delta : the hypothesis on the private exponent (0.263 ~ 0.292) | |
@m : size of the lattice (bigger the better/slower) | |
''' | |
t = int((1-2*delta) * m) # optimization from Herrmann and May | |
X = 2*floor(N^delta) # this _might_ be too much | |
Y = floor(N^(2/3)) # correct if p, q are ~ same size | |
P.<x,y> = PolynomialRing(ZZ) | |
A = int((N+1)/2) | |
pol = 1 + x * (A + y) | |
# Find the solutions! | |
# Checking bounds | |
if debug: | |
print("=== checking values ===") | |
print("* delta:", delta) | |
print("* delta < 0.292", delta < 0.292) | |
print("* size of e:", int(log(e)/log(2))) | |
print("* size of N:", int(log(N)/log(2))) | |
print("* m:", m, ", t:", t) | |
# boneh_durfee | |
if debug: | |
print("=== running algorithm ===") | |
start_time = time.time() | |
solx, soly = boneh_durfee_algorithm(pol, e, m, t, X, Y) | |
if solx > 0: | |
print("=== solutions found ===") | |
if debug: | |
print("x:", solx) | |
print("y:", soly) | |
d = int(pol(solx, soly) / e) | |
return str(d) | |
else: | |
return str(0) | |
if debug: | |
print("=== %s seconds ===" % (time.time() - start_time)) | |
if __name__ == "__main__": | |
N = 1696852658826990842058316561963467335977986730245296081842693913454799128341723605666024757923000936875008280288574503060506225324560725525210728761064310034604441130912702077320696660565727540525259413564999213382434231194132697630244074950529107794905761549606578049632101483460345878198682237227139704889943489709170676301481918176902970896183163611197618458670928730764124354693594769219086662173889094843054787693685403229558143793832013288487194871165461567 | |
e = 814161885590044357190593282132583612817366020133424034468187008267919006610450334193936389251944312061685926620628676079561886595567219325737685515818965422518820810326234612624290774570873983198113409686391355443155606621049101005048872030700143084978689888823664771959905075795440800042648923901406744546140059930315752131296763893979780940230041254506456283030727953969468933552050776243515721233426119581636614777596169466339421956338478341355508343072697451 | |
print(boneh_durfee(N, e, delta=.166, m=5)) | |
print(boneh_durfee(N, e, delta=.1, m=8)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment