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 srp_kexch(self): a = random.getrandbits(1536) A = modexp(self.g, a, self.n) self.salt, B = self.parley("kexch", self.email, A) u_bytes = sha256(to_bytes(A, B)).digest() u = from_bytes(u_bytes) x_bytes = sha256(to_bytes(self.salt) + self.password.encode()).digest() x = from_bytes(x_bytes) S = modexp(B - self.k * modexp(self.g, x, self.n), a + u * x, self.n) self.K = sha256(to_bytes(S)).digest()
def srp_authn(self, email, input_hmac): account = self.accounts[email] salt = account["salt"] salt_bytes = to_bytes(salt) A, b, u = account["A"], account["b"], account["u"] for password in WORDS: # Find x as in Steven's `hello` step/Carla's `kexch` step. x_bytes = sha256(salt_bytes + password.encode()).digest() x = from_bytes(x_bytes) # Find v as in Steven's `hello` step. v = modexp(self.g, x, self.n) # Find S and K as in Steven's `kexch` step. S = modexp(A * modexp(v, u, self.n), b, self.n) K = sha256(to_bytes(S)).digest() if hmac_sha256(K, salt_bytes) == input_hmac: return f"{email}, your password is '{password}'. Muahahaha!" # Password wasn't found in the dictionary. return f"These are not the droids you're looking for."
def srp_hello(self, email, password): salt = random.getrandbits(32) x_bytes = sha256(to_bytes(salt) + password.encode()).digest() x = from_bytes(x_bytes) v = modexp(self.g, x, self.n) self.accounts[email] = {"password": password, "salt": salt, "v": v} return self.n, self.g, self.k
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 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 srp_kexch(self): a = random.getrandbits(1536) A = modexp(self.g, a, self.n) # Carol now receives u from Steve. self.salt, B, u = self.parley("kexch", self.email, A) x_bytes = sha256(to_bytes(self.salt) + self.password.encode()).digest() x = from_bytes(x_bytes) # k is not used anymore. S = modexp(B, a + u * x, self.n) self.K = sha256(to_bytes(S)).digest()
def forge_rsa_e3(bs, pubkey, hash_cls=SHA1): e, n = pubkey n_byte_length = byte_length(n) # Создание дополненного хэша, который начинается с "0x0001ff00 <ASN.1> <ptext_digest>", # заканчивается практически чем угодно, и имеет такой же размер, как и модуль. phash = b"\x00\x01\xff" + ASN_1 + hash_cls(bs).digest() phash = phash.ljust(n_byte_length, b"\xff") # Найти целое число от корня этого хэша; это не должно быть идеально. eth_root = nth_root(from_bytes(phash), e) forged_sig = to_bytes(eth_root) # Окончательная поддельная подпись корректна по праву и равна модулю. return forged_sig.rjust(n_byte_length, b"\x00")
def forge_rsa_e3(bs, pubkey, hash_cls=SHA1): e, n = pubkey n_byte_length = byte_length(n) # Craft a padded hash that begins with "0x0001ff00<ASN.1><ptext_digest>", # ends with pretty much anything, and is as long as the modulus. phash = b"\x00\x01\xff" + ASN_1 + hash_cls(bs).digest() phash = phash.ljust(n_byte_length, b"\xff") # Find the integer e'th root of this hash; it doesn't need to be perfect. eth_root = nth_root(from_bytes(phash), e) forged_sig = to_bytes(eth_root) # Final forged signature is right-justified to be as long as the modulus. return forged_sig.rjust(n_byte_length, b"\x00")
def srp_kexch(self, email, A): account = self.accounts[email] b = random.getrandbits(1536) B = self.k * account["v"] + modexp(self.g, b, self.n) u_bytes = sha256(to_bytes(A, B)).digest() u = from_bytes(u_bytes) # We can't simply calculate the `A * v ** u` part of # `S = (A * v ** u) ** b % n`; the result is huge. But it seems # that all arithmetic is performed modulo n of late. *hint hint* S = modexp(A * modexp(account["v"], u, self.n), b, self.n) account["K"] = sha256(to_bytes(S)).digest() return account["salt"], B
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)