예제 #1
0
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));
예제 #2
0
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]))
예제 #3
0
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
예제 #4
0
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)
예제 #5
0
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
예제 #6
0
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