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 main(): messages = [b"", b"The quick brown fox jumps over the lazy dog", b"BEES"] print("Testing the SHA1 class.") print() for message in messages: print("Message: ", message) print("Expected:", sha1(message).hexdigest()) print("Actual: ", SHA1(message).hexdigest()) print() print() print("Generating a secret-prefix MAC.") message = b"The quick brown fox jumps over the lazy cog" key = b"An arbitrary key" pmac = make_sha1_pmac(message, key) print() print("Message: ", message) print("Key: ", key) print("MAC: ", pmac) print("Authenticates:", check_sha1_pmac(message, key, pmac)) print() print("MAC still authenticates after modifying message?") message2 = bytes(reversed(message)) print() print("Message: ", message2) print("Key: ", key) print("MAC: ", pmac) print("Authenticates:", check_sha1_pmac(message2, key, pmac))
def main(): print("First, we'll test our DSA implementation.") _test_dsa() print() print("Now, let's bruteforce a DSA private key from a 16-bit subkey.") print() ptext = ( b"For those that envy a MC it can be hazardous to your health\n" b"So be friendly, a matter of life and death, just like a etch-a-sketch\n" ) sig = [ 548099063082341131477253921760299949438196259240, 857042759984254168557880549501802188789837994940, ] print(f"Plaintext:") print_indent(*ptext.split(b"\n"), width=70, as_hex=False) print(f"Bruteforced private key:") privkey = bruteforce_dsa_privkey(ptext, sig) print_indent(privkey, as_hex=False) # This part is a little convoluted, but the SHA-1 hashes mentioned # in the challenge must've been mentioned ~for a reason~!! known_sha1s = [ "d2d0714f014a9784047eaeccf956520045c45265", "0954edd5e0afe5542a4adf012611a91912a3ec16", ] sha1s = [ SHA1(ptext).hexdigest(), SHA1(to_hexstring(to_bytes(privkey))).hexdigest() ] print( "Calculated hashes match for plaintext and private key:", all(a == b for a, b in zip(known_sha1s, sha1s)), )
def main(): orig_msg = ( b"comment1=cooking%20MCs;userdata=foo;" b"comment2=%20like%20a%20pound%20of%20bacon" ) key = random.choice(WORDS) pmac = make_sha1_pmac(orig_msg, key) print("First, let's verify that a message authenticates.") print() print("Message: ", orig_msg) print("Key: ", "<secret>") print("MAC: ", pmac) print("Authenticates:", check_sha1_pmac(orig_msg, key, pmac)) print() print() print("Now for the attack.") extra_data = b";admin=true" crafted_pmac = SHA1() # Our result object. crafted_msg, h, key_length = b"", pmac.h.copy(), None # Since we don't know the key's bit length, we have to guess at it. for klen in range(1, 256): # Set "initial" state from original hash. crafted_pmac.h = h # The bit length of the key affects all of these. glue_padding = sha1_padding(orig_msg, klen) crafted_msg = orig_msg + glue_padding + extra_data padded = extra_data + sha1_padding(crafted_msg, klen) # Calculate hash of this particular crafted message. chunks = (padded[i : i + 64] for i in range(0, len(padded), 64)) crafted_pmac._process(chunks) if check_sha1_pmac(crafted_msg, key, crafted_pmac): key_length = klen break else: print("Unfortunately, we failed.") return print() print("Message: ", crafted_msg) print("Key: ", "<still secret>") print("Key length: ", key_length) # We guessed this. print("MAC: ", crafted_pmac) print("Authenticates:", check_sha1_pmac(crafted_msg, key, crafted_pmac))
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 _aes_key(self, s=None): """Generates 128-bit key for AES from DH session key.""" s = self.dh_shared if s is None else s return SHA1(s).digest()[:16]
def check_sha1_pmac(msg, key, pmac): return SHA1(key + msg) == pmac
def make_sha1_pmac(msg, key): return SHA1(key + msg)