def fractional_CRT_split(residues, ps_roots, K, BI_coordinates = None): if K is QQ: return fractional_CRT_QQ(residues, ps_roots); OK = K.ring_of_integers(); BOK = OK.basis(); residues_ZZ = [ r.lift() for r in residues]; primes = [p for p, _ in ps_roots]; lift = CRT_list(residues_ZZ, primes); lift_coordinates = OK.coordinates(lift); if BI_coordinates is None: p_ideals = [ OK.ideal(p, K.gen() - root) for p, root in ps_roots ]; I = prod(p_ideals); BI = I.basis(); # basis as a ZZ-module BI_coordinates = [ OK.coordinates(b) for b in BI ]; M = Matrix(Integers(), [ [ kronecker_delta(i,j) for j,_ in enumerate(BOK) ] + [ lift_coordinates[i] ] + [ b[i] for b in BI_coordinates ] for i in range(len(BOK)) ]) # v = short_vector Kernel_Basis = Matrix(Integers(), M.transpose().kernel().basis()) v =Kernel_Basis.LLL()[0]; #print v[:len(BOK)] if v[len(BOK)] == 0: return 0; return (-1/v[len(BOK)]) * sum( v[i] * b for i, b in enumerate(BOK));
def attack(p, t, a, B): """ Solves the hidden number problem using an attack based on the shortest vector problem. The hidden number problem is defined as finding y such that {x_i - t_i * y + a_i = 0 mod p}. More information: Breitner J., Heninger N., "Biased Nonce Sense: Lattice Attacks against Weak ECDSA Signatures in Cryptocurrencies" :param p: the modulus :param t: the t_i values :param a: the a_i values :param B: a bound on the x values :return: a tuple containing y, and a list of x values """ assert len(t) == len(a), "t and a lists should be of equal length." m = len(t) M = Matrix(QQ, m + 2, m + 2) for i in range(m): M[i, i] = p M[m] = t + [B / QQ(p), 0] M[m + 1] = list(map(lambda x: x - B // 2, a)) + [0, B] L = M.LLL() for row in L.rows(): y = (int(row[m] * p) // B) % p if y != 0 and row[m + 1] == B: return int(y), list(map(lambda x: int(x + B // 2), row[:m]))
def modular_univariate(f, N, m, t, X, early_return=True): """ Computes small modular roots of a univariate polynomial. More information: May A., "New RSA Vulnerabilities Using Lattice Reduction Methods (Section 3.2)" :param f: the polynomial :param N: the modulus :param m: the amount of g shifts to use :param t: the amount of h shifts to use :param X: an approximate bound on the roots :param early_return: try to return as early as possible (default: true) :return: a generator generating small roots of the polynomial """ f = f.monic().change_ring(ZZ) x = f.parent().gen() d = f.degree() B = Matrix(ZZ, d * m + t) row = 0 logging.debug("Generating g shifts...") for i in range(m): for j in range(d): g = x**j * N**(m - i) * f**i for col in range(row + 1): B[row, col] = g(x * X)[col] row += 1 logging.debug("Generating h shifts...") for i in range(t): h = x**i * f**m h = h(x * X) for col in range(row + 1): B[row, col] = h[col] row += 1 logging.debug("Executing the LLL algorithm...") B = B.LLL() logging.debug("Reconstructing polynomials...") for row in range(B.nrows()): new_polynomial = 0 for col in range(B.ncols()): new_polynomial += B[row, col] * x**col if new_polynomial.is_constant(): continue new_polynomial = new_polynomial(x / X).change_ring(ZZ) for x0, _ in new_polynomial.roots(): yield int(x0) if early_return: # Assuming that the first "good" polynomial in the lattice doesn't provide roots, we return. return
def dsa_known_middle(n, signature1, signature2, nonce_bitsize, msb_unknown, lsb_unknown): """ Recovers the (EC)DSA private key and nonces if the middle nonce bits are known. This is a heuristic extension which might perform worse than the methods to solve the Extended Hidden Number Problem More information: De Micheli G., Heninger N., "Recovering cryptographic keys from partial information, by example" (Section 5.2.3) :param n: the modulus :param signature1: the first signature (a tuple of the message (hash), the r value, the s value, and the known middle bits) :param signature2: the second signature (a tuple of the message (hash), the r value, the s value, and the known middle bits) :param nonce_bitsize: the amount of bits of the nonces :param msb_unknown: the amount of unknown most significant bits of the nonces :param lsb_unknown: the amount of unknown least significant bits of the nonces :return: a tuple containing the private key, the nonce of the first signature, and the nonce of the second signature """ unknown = max(msb_unknown, lsb_unknown) K = 2**unknown l = nonce_bitsize - msb_unknown h1, r1, s1, a1 = signature1 h2, r2, s2, a2 = signature2 t = -(pow(s1, -1, n) * s2 * r1 * pow(r2, -1, n)) u = pow(s1, -1, n) * r1 * h2 * pow(r2, -1, n) - pow(s1, -1, n) * h1 u_ = 2**lsb_unknown * a1 + 2**lsb_unknown * a2 * t + u B = Matrix(ZZ, 5) B[0] = vector(ZZ, [K, K * 2**l, K * t, K * t * 2**l, u_]) B[1] = vector(ZZ, [0, K * n, 0, 0, 0]) B[2] = vector(ZZ, [0, 0, K * n, 0, 0]) B[3] = vector(ZZ, [0, 0, 0, K * n, 0]) B[4] = vector(ZZ, [0, 0, 0, 0, K * n]) B = B.LLL() M = Matrix(ZZ, 4) v = [] for row, vec in enumerate(B[:4]): M[row] = vec[:4].apply_map(lambda x: x // K) v.append(-vec[4]) x1, y1, x2, y2 = M.solve_right(vector(ZZ, v)) k1 = 2**l * y1 + 2**lsb_unknown * a1 + x1 k2 = 2**l * y2 + 2**lsb_unknown * a2 + x2 private_key1 = pow(r1, -1, n) * (s1 * k1 - h1) % n private_key2 = pow(r2, -1, n) * (s2 * k2 - h2) % n assert private_key1 == private_key2 return int(private_key1), int(k1), int(k2)
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 of the modulus and the private exponent, or None if the private exponent was not found """ s = isqrt(n) lattice = Matrix([[e, s], [n, 0]]) basis = lattice.LLL() for row in basis.rows(): d = row[1] // s k = abs(row[0] - e * d) // n d = abs(d) phi = (e * d - 1) // k factors = known_phi.factorize(n, phi) if factors: return *factors, d
def modular_bivariate(p, modulus, m, t, xbound, ybound, early_return=True): """ Computes small modular roots of a bivariate polynomial. More information: Herrmann M., May A., "Maximizing Small Root Bounds by Linearization and Applications to Small Secret Exponent RSA" :param p: the polynomial :param modulus: the modulus :param m: the amount of normal shifts to use :param t: the amount of additional shifts to use :param xbound: an approximate bound on the x roots :param ybound: an approximate bound on the y roots :param early_return: try to return as early as possible (default: true) :return: a generator generating small roots (tuples of x and y roots) of the polynomial """ pr = ZZ["u, x, y"] u, x, y = pr.gens() qr = pr.quotient(x * y + 1 - u) p = qr(p).lift() ubound = xbound * ybound shifts = set() monomials = set() logging.debug("Generating x shifts...") for k in range(m + 1): for i in range(m - k + 1): shift = x**i * p**k * modulus**(m - k) shifts.add(shift) for monomial in shift.monomials(): monomials.add(monomial) logging.debug("Generating y shifts...") for j in range(1, t + 1): for k in range(m // t * j, m + 1): shift = y**j * p**k * modulus**(m - k) shift = qr(shift).lift() shifts.add(shift) monomial = u**k * y**j monomials.add(monomial) shifts = sorted(shifts) monomials = sorted(monomials) logging.debug(f"Filling the lattice ({len(shifts)} x {len(monomials)})...") lattice = Matrix(len(shifts), len(monomials)) for row, shift in enumerate(shifts): for col, monomial in enumerate(monomials): lattice[row, col] = shift.monomial_coefficient(monomial) * monomial( ubound, xbound, ybound) logging.debug("Executing the LLL algorithm...") basis = lattice.LLL() logging.debug("Reconstructing polynomials...") v, w = ZZ["v, w"].gens() new_polynomials = [] for row in range(basis.nrows()): # Reconstruct the polynomial from reduced basis new_polynomial = 0 for col, monomial in enumerate(monomials): new_polynomial += basis[row, col] * monomial( v * w + 1, v, w) // monomial(ubound, xbound, ybound) new_polynomials.append(new_polynomial) logging.debug("Generating resultants...") for p1 in new_polynomials: for p2 in new_polynomials: resultant = p1.resultant(p2, w) if not resultant.is_constant(): for vroot, _ in resultant.univariate_polynomial().roots(): vroot = int(vroot) p = p1.subs({v: vroot}) if not p.is_constant(): for wroot, _ in p.univariate_polynomial().roots(): wroot = int(wroot) yield vroot, wroot if early_return: return