def compute_params(s, m=None, a=None, b=None): """Compute parameters and initial seed for LCG prng next_state = a*state + b mod m Args: s(list): subsequent outputs from LCG oracle starting with seed m(int/None) a(int/None) b(int/None) Returns: a, b, m(int) """ if m is None: t = [s[n + 1] - s[n] for n in range(len(s) - 1)] u = [abs(t[n + 2] * t[n] - t[n + 1]**2) for n in range(len(t) - 2)] m = gcd(*u) log.success("m = {}".format(m)) if a is None: if gcd(s[1] - s[0], m) == 1: a = (s[2] - s[1]) * invmod(s[1] - s[0], m) elif gcd(s[2] - s[0], m) == 1: a = (s[3] - s[1]) * invmod(s[2] - s[0], m) else: log.critical_error("a not found") a = a % m log.success("a = {}".format(a)) if b is None: b = (s[1] - s[0] * a) % m log.success("b = {}".format(b)) return a, b, m
def factors_from_d(n, e, d): """Factorize n to p and q given e and d""" k = e * d - 1 while True: g = random.randint(2, n - 2) b = k // (2**power_of_two(k)) while b < k: gb = gmpy2.powmod(g, b, n) if gb != 1 and gb != n - 1 and gmpy2.powmod(gb, 2, n) == 1: if gcd(gb - 1, n) != 1: p = gcd(gb - 1, n) else: p = gcd(gb + 1, n) return p, n // p b *= 2
def test_wiener(tries=10): print("\nTest: wiener") for _ in range(tries): n_size = 1024 p = random_prime(n_size // 2) q = random_prime(n_size // 2) n = p * q phi = (p - 1) * (q - 1) while True: d = getrandbits(n_size // 4) if gcd(phi, d) == 1 and 81 * pow(d, 4) < n: break e = invmod(d, phi) key = RSAKey.construct(int(n), int(e)) key_recovered = wiener(key.publickey()) if key_recovered: assert key_recovered.d == d else: print("Not recovered")
def guess_key_size(ciphertext, max_key_size=40): """Given sentence xored with short key, guess key size From: http://trustedsignal.blogspot.com/2015/06/xord-play-normalized-hamming-distance.html Args: ciphertext(string) max_key_size(int) Returns: list: sorted list of tuples (key_size, probability), note that most probable key size not necessary have the largest probability """ if not max_key_size: max_key_size = len(ciphertext)/4 result = {} for key_size in range(1, max_key_size): blocks = re.findall('.' * key_size, ciphertext, re.DOTALL) if len(blocks) < 2: break diff = i = 0 while i < len(blocks) - 1: if len(blocks[i]) != len(blocks[i + 1]): # not full-length block break diff += hamming_distance(blocks[i], blocks[i + 1]) i += 1 result[key_size] = diff / float(i) # average result[key_size] /= float(key_size) # normalize result = sorted(list(result.items()), key=operator.itemgetter(1)) # now part from given link, case one # gcd12 = gcd(result[0][0], result[1][0]) # gcd13 = gcd(result[0][0], result[2][0]) # gcd23 = gcd(result[1][0], result[2][0]) # print gcd12, gcd13, gcd23 # if (gcd12 != 1) and (gcd12 in [x[0] for x in result[:5]]): # if (gcd12 == gcd13 and gcd12 == gcd23) or (gcd12 == result[0][0] or gcd12 == result[1][0]): # #remove key_size == gcd12 from result list and add it to the beginning # for x in result: # if x[0] == gcd12: # result.remove(x) # break # result[0] == (gcd12, 1.0) # from link, case two; yep, black magic it is gcd_frequencies = defaultdict(lambda: 0) for gcd_pairs in itertools.combinations(result[:10], 2): gcd_tmp = gcd(gcd_pairs[0][0], gcd_pairs[1][0]) gcd_frequencies[gcd_tmp] += 1 gcd_frequencies = sorted(list(gcd_frequencies.items()), key=operator.itemgetter(1), reverse=True) key_sizes = [x[0] for x in result[:10]] distances = [x[1] for x in result[:10]] for guessed_most_probable_key_size in gcd_frequencies[:5]: if guessed_most_probable_key_size[0] != 1 and guessed_most_probable_key_size[1] != 0 and \ guessed_most_probable_key_size[0] in key_sizes: gmks_position = result[key_sizes.index(guessed_most_probable_key_size[0])] if gmks_position[1] < max(distances): result.remove(gmks_position) result = [gmks_position] + result log.info("Guessed key size: {}".format(result)) return result