def bruteforce_dsa_privkey(bs, sig, max_k=2**16, p=P, q=Q, g=G): r, s = sig z = from_bytes(SHA1(bs).digest()) for k in range(1, max_k): k_inv = invmod(k, q) if k_inv is None: continue x = (((((s * k) % q) - z) % q) * invmod(r, q)) % q sk, rk = (k_inv * (z + x * r)) % q, modexp(g, k, p) % q if s == sk and r == rk: return x
def recover_dsa_privkey(k, r, s, z, p=P, q=Q, g=G): k_inv = invmod(k, q) if k_inv is None: return None x = (((((s * k) % q) - z) % q) * invmod(r, q)) % q # This check isn't necessary. :) # from util.misc import modexp # # sk, rk = (k_inv * (z + x * r)) % q, modexp(g, k, p) % q # if s == sk and r == rk: # return x return x
def rsa_echo(self, ctext): # Eve is actually MITMing, not just eavesdropping. # To simulate an eavesdrop, have Eve discard the plaintext. self.parley("echo", ctext) # Steve will reject repeat submissions. assert self.parley("echo", ctext) is None # Eve cracks the ciphertext on her own. C = from_bytes(ctext) E, N = self.remote_pubkey S = random.randint(2, N - 1) C_ = (C * modexp(S, E, N)) % N ptext_ = self.parley("echo", to_bytes(C_)) P_ = from_bytes(ptext_) P = (P_ * invmod(S, N)) % N ptext = to_bytes(P) print("Eve cracked Carol's plaintext:") print() print(" " * 4 + ptext.decode()) print() # Carol won't notice a thing :P return ptext
def _test_invmod(): tests = [ [42, 2017, 1969], [40, 1, 0], [52, -217, 96], [-486, 217, 121], [40, 2018, None], ] if all(invmod(a, b) == expected for a, b, expected in tests): return "Passed." return "Failed."
def main(): # NOTE BEFORE STARTING: Do not be fooled! Signatures are created using # private keys, notwithstanding the (perhaps intentionally misleading?) # wording in this and the previous challenge that might imply otherwise. lines = loader("44.txt", lambda l: l.rstrip("\n").split(": ")) msgs = [] for i in range(0, len(lines), 4): block = lines[i:i + 4] # After the rstrip() and split() above, a block looks like: # # [["msg", "Listen for me, you better listen for me now. "], # ["s", "1267396447369736888040262262183731677867615804316"], # ["r", "1105520928110492191417703162650245113664610474875"], # ["m", "a4db3de27e2db3e5ef085ced2bced91b82e0df19"]] # # We have a message, the components of a DSA signature, and the SHA1 # hash of the message. Everything here's a `str` at the moment, so # we'll transform the values. # # There's an error in "m" for one of the blocks in the challenge data, # but we can just calculate the hash ourselves. block[0][1] = block[0][1].encode() block[1][1] = int(block[1][1]) block[2][1] = int(block[2][1]) block[3][1] = from_bytes(SHA1(block[0][1]).digest()) msgs.append({data[0]: data[1] for data in block}) print(f"Loaded {len(msgs)} DSA-signed messages.") print("Recovering private key from the first repeated nonce we detect.") for msg1, msg2 in combinations(msgs, 2): if msg1["r"] != msg2["r"]: continue m1, m2 = msg1["m"], msg2["m"] s1, s2 = msg1["s"], msg2["s"] k = (((m1 - m2) % Q) * invmod(s1 - s2, Q)) % Q privkey = recover_dsa_privkey(k, msg1["r"], s1, m1) digest = SHA1(to_hexstring(to_bytes(privkey))).hexdigest() assert digest == "ca8f6f7c66fa362d40760d135b763eb8527d3d52" print() print("Recovered key:", privkey) break else: print("Failed to recover key!")
def iterate(self): self.i += 1 self.show_progress() self.s = self.find_s() self.M = self.find_ranges() if len(self.M) == 1: a, b = self._M if a == b: self.result = (a * invmod(self.s_0, self.n)) % self.n elif len(self.M) > 1: print(f"(M_{self.i} has {len(self.M)} ranges, mumble mumble~)")
def decrypt_pass(constants, state, oracle): e, n, c_0, s_0, B, B_2, B_3, B_31 = constants i, s_i1, M_i1 = state # Find s_i. if i == 1 or (i > 1 and len(M_i1) > 1): s_i = (n + B_31) // B_3 if i == 1 else s_i1 + 1 while not oracle((c_0 * modexp(s_i, e, n)) % n): s_i += 1 else: a, b = list(M_i1)[0] r_i = ((2 * (b * s_i1 - B_2)) + (n - 1)) // n while True: lower = (B_2 + (r_i * n) + (b - 1)) // b upper = (B_3 + (r_i * n) + (a - 1)) // a for s_i in range(lower, upper): if oracle((c_0 * modexp(s_i, e, n)) % n): break else: r_i += 1 continue break # Find M_i. M_i = set() for a, b in M_i1: lower = ((a * s_i) - B_31 + (n - 1)) // n upper = ((b * s_i) - B_2) // n for r in range(lower, upper + 1): a_i = (B_2 + (r * n) + (s_i - 1)) // s_i b_i = (B_31 + (r * n)) // s_i M_i.add((max(a, a_i), min(b, b_i))) if len(M_i) == 1: a, b = list(M_i)[0] if a == b: return (a * invmod(s_0, n)) % n state = (i + 1, s_i, M_i) return constants, state
def main(): ptext = random.choice(PTEXTS) print("Generating 3 public RSA keys and encrypting a plaintext.") print() n, c = [], [] for i in range(3): pubkey, privkey = make_rsa_keys() ctext = rsa(ptext, pubkey) assert rsa(ctext, privkey) == ptext print(f"Ciphertext {i}:") print_indent(ctext) n.append(pubkey[1]) c.append(from_bytes(ctext)) if len(set(c)) == 1: print("The ciphertexts are sometimes identical.") print("This is fine; we also rely upon the public keys differing.") print() result, n_012 = 0, 1 for i in range(3): ms = n[(i + 1) % 3] * n[(i + 2) % 3] result += c[i] * ms * invmod(ms, n[i]) n_012 *= n[i] result = nth_root(result % n_012, 3) print("Cracked plaintext:") print_indent(to_bytes(result), as_hex=False)