Path: blob/master/attacks/factorization/branch_and_prune.py
2589 views
import logging1import os2import sys3from itertools import product45from sage.all import Zmod67path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))8if sys.path[1] != path:9sys.path.insert(1, path)1011from shared import bits_to_int_le12from shared import int_to_bits_le131415# Section 3.16def _tau(x):17i = 018while x % 2 == 0:19x //= 220i += 12122return i232425# Section 2.26def _find_k(N, e, d_bits):27best_match_count = 028best_k = None29best_d__bits = None30# Enumerate every possible k value.31for k in range(1, e):32d_ = (k * (N + 1) + 1) // e33d__bits = int_to_bits_le(d_, len(d_bits))34match_count = 035# Only check the most significant half.36for i in range(len(d_bits) // 2 + 2, len(d_bits)):37if d_bits[i] == d__bits[i]:38match_count += 13940# Update the best match for d.41if match_count > best_match_count:42best_match_count = match_count43best_k = k44best_d__bits = d__bits4546return best_k, best_d__bits474849# Section 2.50def _correct_msb(d_bits, d__bits):51# Correcting the most significant half of d.52for i in range(len(d_bits) // 2 + 2, len(d_bits)):53d_bits[i] = d__bits[i]545556# Section 3.57def _correct_lsb(e, d_bits, exp):58# Correcting the least significant bits of d.59# Also works for dp and dq, just with a different exponent.60inv = pow(e, -1, 2 ** exp)61for i in range(exp):62d_bits[i] = (inv >> i) & 1636465# Branch and prune for the case with p and q bits known.66def _branch_and_prune_pq(N, p, q, p_, q_, i):67if i == len(p) or i == len(q):68yield p_, q_69else:70c1 = ((N - p_ * q_) >> i) & 171p_prev = p[i]72q_prev = q[i]73p_possible = [0, 1] if p_prev is None else [p_prev]74q_possible = [0, 1] if q_prev is None else [q_prev]75for p_bit, q_bit in product(p_possible, q_possible):76# Addition modulo 2 is just xor.77if p_bit ^ q_bit == c1:78p[i] = p_bit79q[i] = q_bit80yield from _branch_and_prune_pq(N, p, q, p_ | (p_bit << i), q_ | (q_bit << i), i + 1)8182p[i] = p_prev83q[i] = q_prev848586# Branch and prune for the case with p, q, and d bits known.87def _branch_and_prune_pqd(N, e, k, tk, p, q, d, p_, q_, i):88if i == len(p) or i == len(q):89yield p_, q_90else:91d_ = bits_to_int_le(d, i)92c1 = ((N - p_ * q_) >> i) & 193c2 = ((k * (N + 1) + 1 - k * (p_ + q_) - e * d_) >> (i + tk)) & 194p_prev = p[i]95q_prev = q[i]96d_prev = 0 if i + tk >= len(d) else d[i + tk]97p_possible = [0, 1] if p_prev is None else [p_prev]98q_possible = [0, 1] if q_prev is None else [q_prev]99d_possible = [0, 1] if d_prev is None else [d_prev]100for p_bit, q_bit, d_bit in product(p_possible, q_possible, d_possible):101# Addition modulo 2 is just xor.102if p_bit ^ q_bit == c1 and d_bit ^ p_bit ^ q_bit == c2:103p[i] = p_bit104q[i] = q_bit105if i + tk < len(d):106d[i + tk] = d_bit107yield from _branch_and_prune_pqd(N, e, k, tk, p, q, d, p_ | (p_bit << i), q_ | (q_bit << i), i + 1)108109p[i] = p_prev110q[i] = q_prev111if i + tk < len(d):112d[i + tk] = d_prev113114115# Branch and prune for the case with p, q, d, dp, and dq bits known.116def _branch_and_prune_pqddpdq(N, e, k, tk, kp, tkp, kq, tkq, p, q, d, dp, dq, p_, q_, i):117if i == len(p) or i == len(q):118yield p_, q_119else:120d_ = bits_to_int_le(d, i)121dp_ = bits_to_int_le(dp, i)122dq_ = bits_to_int_le(dq, i)123c1 = ((N - p_ * q_) >> i) & 1124c2 = ((k * (N + 1) + 1 - k * (p_ + q_) - e * d_) >> (i + tk)) & 1125c3 = ((kp * (p_ - 1) + 1 - e * dp_) >> (i + tkp)) & 1126c4 = ((kq * (q_ - 1) + 1 - e * dq_) >> (i + tkq)) & 1127p_prev = p[i]128q_prev = q[i]129d_prev = 0 if i + tk >= len(d) else d[i + tk]130dp_prev = 0 if i + tkp >= len(dp) else dp[i + tkp]131dq_prev = 0 if i + tkq >= len(dq) else dq[i + tkq]132p_possible = [0, 1] if p_prev is None else [p_prev]133q_possible = [0, 1] if q_prev is None else [q_prev]134d_possible = [0, 1] if d_prev is None else [d_prev]135dp_possible = [0, 1] if dp_prev is None else [dp_prev]136dq_possible = [0, 1] if dq_prev is None else [dq_prev]137for p_bit, q_bit, d_bit, dp_bit, dq_bit in product(p_possible, q_possible, d_possible, dp_possible, dq_possible):138# Addition modulo 2 is just xor.139if p_bit ^ q_bit == c1 and d_bit ^ p_bit ^ q_bit == c2 and dp_bit ^ p_bit == c3 and dq_bit ^ q_bit == c4:140p[i] = p_bit141q[i] = q_bit142if i + tk < len(d):143d[i + tk] = d_bit144if i + tkp < len(dp):145dp[i + tkp] = dp_bit146if i + tkq < len(dq):147dq[i + tkq] = dq_bit148yield from _branch_and_prune_pqddpdq(N, e, k, tk, kp, tkp, kq, tkq, p, q, d, dp, dq, p_ | (p_bit << i), q_ | (q_bit << i), i + 1)149150p[i] = p_prev151q[i] = q_prev152if i + tk < len(d):153d[i + tk] = d_prev154if i + tkp < len(dp):155dp[i + tkp] = dp_prev156if i + tkq < len(dq):157dq[i + tkq] = dq_prev158159160def factorize_pq(N, p, q):161"""162Factorizes n when some bits of p and q are known.163If at least 57% of the bits are known, this attack should be polynomial time, however, smaller percentages might still work.164More information: Heninger N., Shacham H., "Reconstructing RSA Private Keys from Random Key Bits"165:param N: the modulus166:param p: partial p (PartialInteger)167:param q: partial q (PartialInteger)168:return: a tuple containing the prime factors169"""170assert p.bit_length == q.bit_length, "p and q should be of equal bit length."171172p_bits = p.to_bits_le()173for i, b in enumerate(p_bits):174p_bits[i] = None if b == '?' else int(b, 2)175176q_bits = q.to_bits_le()177for i, b in enumerate(q_bits):178q_bits[i] = None if b == '?' else int(b, 2)179180# p and q are prime, odd.181p_bits[0] = 1182q_bits[0] = 1183184logging.info("Starting branch and prune algorithm...")185for p, q in _branch_and_prune_pq(N, p_bits, q_bits, p_bits[0], q_bits[0], 1):186if p * q == N:187return int(p), int(q)188189190def factorize_pqd(N, e, p, q, d):191"""192Factorizes n when some bits of p, q, and d are known.193If at least 42% of the bits are known, this attack should be polynomial time, however, smaller percentages might still work.194More information: Heninger N., Shacham H., "Reconstructing RSA Private Keys from Random Key Bits"195:param N: the modulus196:param e: the public exponent197:param p: partial p (PartialInteger)198:param q: partial q (PartialInteger)199:param d: partial d (PartialInteger)200:return: a tuple containing the prime factors201"""202assert p.bit_length == q.bit_length, "p and q should be of equal bit length."203204p_bits = p.to_bits_le()205for i, b in enumerate(p_bits):206p_bits[i] = None if b == '?' else int(b, 2)207208q_bits = q.to_bits_le()209for i, b in enumerate(q_bits):210q_bits[i] = None if b == '?' else int(b, 2)211212# p and q are prime, odd.213p_bits[0] = 1214q_bits[0] = 1215216d_bits = d.to_bits_le()217for i, b in enumerate(d_bits):218d_bits[i] = None if b == '?' else int(b, 2)219220# Because e is small, k can be found by brute force.221logging.info("Brute forcing k...")222k, d__bits = _find_k(N, e, d_bits)223logging.info(f"Found {k = }")224225_correct_msb(d_bits, d__bits)226227tk = _tau(k)228_correct_lsb(e, d_bits, 2 + tk)229230logging.info("Starting branch and prune algorithm...")231for p, q in _branch_and_prune_pqd(N, e, k, tk, p_bits, q_bits, d_bits, p_bits[0], q_bits[0], 1):232if p * q == N:233return int(p), int(q)234235236def factorize_pqddpdq(N, e, p, q, d, dp, dq):237"""238Factorizes n when some bits of p, q, d, dp, and dq are known.239If at least 27% of the bits are known, this attack should be polynomial time, however, smaller percentages might still work.240More information: Heninger N., Shacham H., "Reconstructing RSA Private Keys from Random Key Bits"241:param N: the modulus242:param e: the public exponent243:param p: partial p (PartialInteger)244:param q: partial q (PartialInteger)245:param d: partial d (PartialInteger)246:param dp: partial dp (PartialInteger)247:param dq: partial dq (PartialInteger)248:return: a tuple containing the prime factors249"""250assert p.bit_length == q.bit_length, "p and q should be of equal bit length."251252p_bits = p.to_bits_le()253for i, b in enumerate(p_bits):254p_bits[i] = None if b == '?' else int(b, 2)255256q_bits = q.to_bits_le()257for i, b in enumerate(q_bits):258q_bits[i] = None if b == '?' else int(b, 2)259260# p and q are prime, odd.261p_bits[0] = 1262q_bits[0] = 1263264d_bits = d.to_bits_le()265for i, b in enumerate(d_bits):266d_bits[i] = None if b == '?' else int(b, 2)267268# Because e is small, k can be found by brute force.269logging.info("Brute forcing k...")270k, d__bits = _find_k(N, e, d_bits)271logging.info(f"Found {k = }")272273_correct_msb(d_bits, d__bits)274275tk = _tau(k)276_correct_lsb(e, d_bits, 2 + tk)277278x = Zmod(e)["x"].gen()279f = x ** 2 - x * (k * (N - 1) + 1) - k280logging.info("Computing kp and kq...")281for kp in f.roots(multiplicities=False):282kp = int(kp)283kq = (-pow(kp, -1, e) * k) % e284logging.info(f"Trying {kp = } and {kq = }...")285286# Make a copy for every try of kp and kq so we are sure these bits are not modified.287# We don't need to make a copy of p, q, and d bits in this loop because those bits only get modified in the branch and prune.288# The branch and prune algorithm always resets the bits after recursion.289dp_bits = dp.to_bits_le()290for i, b in enumerate(dp_bits):291dp_bits[i] = None if b == '?' else int(b, 2)292293dq_bits = dq.to_bits_le()294for i, b in enumerate(dq_bits):295dq_bits[i] = None if b == '?' else int(b, 2)296297tkp = _tau(kp)298_correct_lsb(e, dp_bits, 1 + tkp)299tkq = _tau(kq)300_correct_lsb(e, dq_bits, 1 + tkq)301302logging.info("Starting branch and prune algorithm...")303for p, q in _branch_and_prune_pqddpdq(N, e, k, tk, kp, tkp, kq, tkq, p_bits, q_bits, d_bits, dp_bits, dq_bits, p_bits[0], q_bits[0], 1):304if p * q == N:305return int(p), int(q)306307308