def common_modulus_attack(cpair, epair, n): """Common Modulus Attack Given 2 (or more) ciphertext of same plaintext with different e, we can decrypt the ciphertext using Extended Euclid Algorithm. """ if len(cpair) < 2 or len(epair) < 2: logger.warn("cpair and epair must have 2 or more elements.") return None c1, c2 = cpair[0], cpair[1] _, s1, s2 = xgcd(epair[0], epair[1]) if s1 < 0: s1 = -s1 c1 = inverse(c1, n) elif s2 < 0: s2 = -s2 c2 = inverse(c2, n) return (pow(c1, s1, n) * pow(c2, s2, n)) % n
def add(P, Q): if Q == infty: return P if P == infty: return Q x_1, y_1 = P x_2, y_2 = Q if x_1 != x_2: m = (y_2 - y_1) * invert(x_2 - x_1, p) % p x_3 = (pow(m, 2, p) - x_1 - x_2) % p y_3 = (m * (x_1 - x_3) - y_1) % p return (x_3, y_3) elif y_1 != y_2: return infty elif y_1 != 0: m = (3 * pow(x_1, 2, p) - 3) * invert(2 * y_1, p) % p x_3 = (pow(m, 2, p) - 2 * x_1) % p y_3 = (m * (x_1 - x_3) - y_1) % p return (x_3, y_3) else: return infty
def faulty(key, padding=None): """Faulty attack against crt-rsa, Boneh-DeMillo-Lipton sp = padding(m)**(d % p-1) % p sq' = padding(m)**(d % q-1) % q <--any error during computation s' = crt(sp, sq') % n <-- broken signature s = crt(sp, sq) % n <-- correct signature p = gcd(s'**e - padding(m), n) p = gcd(s - s', n) Args: key(RSAKey): with at least one broken signature (key.texts[no]['cipher']) and corresponding plaintext (key.texts[no]['plain']), or valid and broken signature padding(None/function): function used before signing message Returns: NoneType/RSAKey: False on failure, recovered private key otherwise """ log.debug("Check signature-message pairs") for pair in key.texts: if 'plain' in pair and 'cipher' in pair: signature = gmpy2.mpz(pair['cipher']) message = pair['plain'] if padding: message = padding(message) p = gmpy2.gcd(gmpy2.pow(signature, key.e) - message, key.n) if p != 1 and p != key.n: log.info("Found p={}".format(p)) new_key = RSAKey.construct(key.n, key.e, p=p, identifier=key.identifier + '-private') new_key.texts = key.texts[:] return new_key log.debug("Check for valid-invalid signatures") signatures = [tmp['cipher'] for tmp in key.texts if 'cipher' in tmp] for pair in itertools.combinations(signatures, 2): p = gmpy2.gcd(pair[0] - pair[1], key.n) if p != 1 and p != key.n: log.info("Found p={}".format(p)) new_key = RSAKey.construct(key.n, key.e, p=p, identifier=key.identifier + '-private') new_key.texts = key.texts[:] return new_key return None
def lsb_leak_attack(lsb_oracle, n, e, c): """RSA LSB Leak Attack Given a cryptosystem such that: - Using the "textbook" RSA (RSA without pading) - We can give any ciphertexts to decrypt and can get the least significant bit of decrypted plaintext. - We can try to decrypt ciphertexts without limit we can break the ciphertext with LSB Leak Attack(We should make name more cool) Usage: plain = padding_oracle(lsb_oracle, N, e, C) The function lsb_oracle must return LSB (1 or 0). """ logger = getLogger(__name__) L = n.bit_length() t = L // 100 left, right = 0, n c2 = c i = 0 while right - left > 1: m = Fraction(left + right, 2) c2 = (c2 * pow(2, e, n)) % n oracle = lsb_oracle(c2) if oracle == 1: left = m elif oracle == 0: right = m else: raise ValueError("The function `lsb_oracle` must return 1 or 0") i += 1 if i % t == 0: logger.info("LSB Leak Attack {}/{}".format(i, L)) assert (i <= L) return int(ceil(left))
def bleichenbacher_pkcs15(pkcs15_padding_oracle, key, ciphertext=None, incremental_blinding=False, **kwargs): """Given oracle that checks if ciphertext decrypts to some valid plaintext with PKCS1.5 padding we can decrypt whole ciphertext pkcs15_padding_oracle function must be implemented http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf https://www.dsi.unive.it/~focardi/RSA-padding-oracle/#eq5 Note that this attack is very slow. Approximate number of main loop iterations == key's bit length Args: pkcs15_padding_oracle(callable) key(RSAKey): contains ciphertexts to decrypt incremental_blinding(bool): if ciphertext is not pkcs confirming we need to blind it. this may be done using random or incremental values Returns: dict: decrypted ciphertexts update key texts """ def ceil(a, b): return a // b + (a % b > 0) def floor(a, b): return a // b def insert_interval(M, lb, ub): lo, hi = 0, len(M) while lo < hi: mid = (lo + hi) // 2 if M[mid][0] < lb: lo = mid + 1 else: hi = mid # insert it M.insert(lo, (lb, ub)) # lb inside previous interval if lo > 0 and M[lo - 1][1] >= lb: lb = min(lb, M[lo - 1][0]) M[lo] = (lb, M[lo][1]) del M[lo - 1] lo -= 1 # remove covered intervals i = lo + 1 to_remove_first = i to_remove_last = lo while i < len(M) and M[i][0] <= ub: to_remove_last += 1 i += 1 if to_remove_last > lo: new_ub = max(ub, M[to_remove_last][1]) M[lo] = (M[lo][0], new_ub) del M[to_remove_first:to_remove_last + 1] def update_intervals(M, B, s, n): # step 3 M2 = [] for a, b in M: r_min = ceil(a * s - 3 * B + 1, n) r_max = floor(b * s - 2 * B, n) for r in range(r_min, r_max + 1): lb = max(a, ceil(2 * B + r * n, s)) ub = min(b, floor(3 * B - 1 + r * n, s)) insert_interval(M2, lb, ub) del M[:] return M2 def find_si(si_start, si_max=None): si_new = si_start while si_max is None or si_new < si_max: cipheri = (cipher_blinded * gmpy2.powmod(si_new, e, n)) % n if pkcs15_padding_oracle(cipheri, **kwargs): return si_new si_new += 1 return None recovered = {} for text_no, cipher in _prepare_ciphertexts(key=key, ciphertext=ciphertext): log.info("Decrypting {}".format(cipher)) n = key.n e = key.e B = gmpy2.pow(2, key.size - 16) # step 1 log.debug('Blinding the ciphertext (to make it PKCS1.5 confirming)') i = 0 si = 1 cipher_blinded = cipher while not pkcs15_padding_oracle(cipher_blinded, **kwargs): # the paper says to draw it, but seems like incrementation sometimes run faster if incremental_blinding: si += 1 else: si = random.randint(2, 1 << (key.size - 16)) cipher_blinded = (cipher * gmpy2.powmod(si, e, n)) % n Mi = [(2 * B, 3 * B - 1)] s0 = si log.debug('Found s{}: {}'.format(i, hex(si))) i = 1 plaintext = None while plaintext is None: log.debug('len(M{}): {}'.format(i - 1, len(Mi))) if i == 1: # step 2.a si = find_si(si_start=ceil(n, (3 * B))) log.debug('Found s{}: {}'.format(i, hex(si))) elif len(Mi) > 1: # step 2.b si = find_si(si_start=si + 1) elif len(Mi) == 1 and Mi[0][0] != Mi[0][1]: # step 2.c a, b = Mi[0] ri = ceil(2 * (b * si - 2 * B), n) si = None while si is None: si_min = ceil(2 * B + ri * n, b) si_max = ceil(3 * B + ri * n, a) si = find_si(si_start=si_min, si_max=si_max) ri += 1 else: log.error( "Hm, something strange happend. Len(M{}) = {}".format( i, len(Mi))) return None # step 3 Mi = update_intervals(Mi, B, si, n) # step 4 if len(Mi) == 1 and Mi[0][0] == Mi[0][1]: plaintext = Mi[0][0] if s0 != 1: plaintext = (plaintext * invmod(s0, n)) % n log.success("Interval narrowed to one value") log.success("plaintext = {}".format(hex(plaintext))) else: i += 1 recovered[text_no] = plaintext key.texts[text_no]['plain'] = recovered[text_no] return recovered
def is_on_curve(P): if P == infty: return True (x, y) = P return pow(y, 2, p) == (pow(x, 3, p) - 3 * x + b) % p
from __future__ import division import re import imp import math import uuid import gmpy2 import os.path import traceback import __builtin__ from glob import glob from HALapi import get_main_dir, module_filter, HALcannotHandle # Some version of gmpy2 doesn't have pow() try: gmpy2.pow(2, 10) except (AttributeError, TypeError): gmpy2.pow = lambda x, y: x**y gmpy2.context().precision = 256 math_list = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'exp', 'floor', 'fmod', 'hypot', 'modf', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] builtin_list = ['abs'] funcs = dict((name, getattr(gmpy2, name)) for name in math_list) funcs.update(dict((name, getattr(__builtin__, name)) for name in builtin_list)) funcs.update(fact=math.factorial, mpfr=gmpy2.mpfr, arctan=gmpy2.atan, arccos=gmpy2.acos, arcsin=gmpy2.asin, log=gmpy2.log10, ln=gmpy2.log) const = dict(e =gmpy2.mpfr('2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274'), pi=gmpy2.mpfr('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679')) filters = {