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
Exemple #8
0
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)