def challenge_50(): # Create hash function original_key = b'YELLOW SUBMARINE' original_iv = b'\x00' * 16 generate_hash = lambda javascript: set2.encrypt_aes_cbc( javascript, original_key, original_iv, False)[-16:] # Recreate original hash (the one we will forge) original_message = set2.pkcs7_add_padding(b"alert('MZA who was that?');\n", 16) original_hash = generate_hash(original_message) assert original_hash == bytes.fromhex('296b8d7cb78a243dda4d0a61d33bbdd1') # New piece of JavaScript new_message = b"alert('Ayo, the Wu is back!');//" # Find the last cipher block of our new JavaScript (will be the IV of the following block) ciphertext_block_1_2 = set2.encrypt_aes_cbc(new_message, original_key, original_iv, False)[-16:] # Generate a third plaintext block for our new JavaScript, by XORing the found IV with the first plaintext block of the original JavaScript plaintext_block_3 = xor_string(ciphertext_block_1_2[-16:], original_message[:16]) # Now, append the found block plus the remaining blocks of the original JavaScript new_message += plaintext_block_3 + original_message[16:] # Hashing this will result in the original hash assert_true(generate_hash(new_message) == original_hash)
def challenge_13(): # Get the encryption and decryption oracles enc_oracle, dec_oracle = get_oracles() # Using the encryption oracle, generate two valid blocks containing email, uid in full and role header profile = bytes(profile_for('x' * 13), 'utf-8') ciphertext = enc_oracle(profile) part_1 = ciphertext[:32] # represents: email=xxxxxxxxxxxxx&uid=10&role= # Using the encryption oracle, generate a block that only contains 'admin' and padding characters profile = bytes(profile_for('x' * 10 + "admin" + (chr(11) * 11)), 'utf-8') ciphertext = enc_oracle(profile) part_2 = ciphertext[16:32] # represents: admin\x0B*11 # Concatenate the two parts to get ciphertext for: # email=xxxxxxxxxxxxx&uid=10&role=admin\x0B*11 constructed_ciphertext = part_1 + part_2 # Using the decryption oracle and the constructed ciphertext, get the parsed profile plaintext = str(dec_oracle(constructed_ciphertext), 'utf-8') print(plaintext) created_profile = query_parser(plaintext) # Make sure the role was spoofed successfully assert_true(created_profile['role'] == 'admin')
def challenge_43(): # Set parameters p, q, g p = 0x800000000000000089e1855218a0e7dac38136ffafa72eda7859f2171e25e65eac698c1702578b07dc2a1076da241c76c62d374d8389ea5aeffd3226a0530cc565f3bf6b50929139ebeac04f48c3c84afb796d61e5a4f9a8fda812ab59494232c7d2b4deb50aa18ee9e132bfa85ac4374d7f9091abc3d015efc871a584471bb1 q = 0xf4f47f05794b256174bba6e9b396a7707e563c5b g = 0x5958c9d3898b224b12672c0b98e06c60df923cb8bc999d119458fef538b8fa4046c8db53039db620c094c9fa077ef389b5322a559946a71903f990f1f7e0e025e2d7f7cf494aff1a0470f5b64c36b625a097f1651fe775323556fe00b3608c887892878480e99041be601a62166ca6894bdd41a7054ec89f756ba9fc95302291 message = b'Who is the king of the jungle?' # Set up new DSA instance and keypair dsa = DSA(p, q, g) x, y = dsa.generate_keypair() # Part 1: try to verify a valid signature sig, k = dsa.sign(message, x) assert dsa.verify(message, sig, y) # Part 2: try and obtain x assert dsa.crack_x(k, message, sig) == x # Part 3: try and brute force private key based on signature and message# message = b'For those that envy a MC it can be hazardous to your health\nSo be friendly, a matter of life and death, just like a etch-a-sketch\n' sig = 548099063082341131477253921760299949438196259240, 857042759984254168557880549501802188789837994940 # Try all `k`s in given range for k in range(0, 2 ** 16): # Try k, get private key `x` x = dsa.crack_x(k, message, sig) # Verify hash of found private key `x` if hashlib.sha1(hex(x)[2:].encode()).hexdigest() == '0954edd5e0afe5542a4adf012611a91912a3ec16': print("The private key is x={}".format(x)) assert_true(True) break else: print('Unable to find x') assert_true(False)
def challenge_40(): e = 3 # For this attack to work, it is assumed that: # - all `p`s and `q`s are unique; # - all `n`s are coprime with each other; # - the message is smaller than the smallest `n`. # The code below will make sure that these conditions are met. while True: # Generate a random message msg = random.randrange(1, 1000) data, pqs = [], set() for _ in range(e): # Generate new public/private key pair public, _ = set_up_rsa(e, pqs) # If we end up getting a public key that's too small for our message, start over if public[1] <= msg: break # Collect encrypted text and public key data.append((encrypt_rsa(msg, public), public[1])) else: # If we succsefully generated `e` ciphertexts and pubkeys, verify the pubkeys are coprime with each other if are_coprime([n for _, n in data]): # If not, start over break # Use CRT and take `e`th root r = round(crt(data) ** (1/e)) print('Original message: {}, found message: {}'.format(msg, r)) assert_true(r == msg)
def challenge_34(): # Simple EchoBot exchange alice = EchoAlice() bob = EchoBob() msg1 = alice.initiate(p=nist_p, g=nist_g) msg2 = bob.receive(msg1) msg3 = alice.send_msg(msg2, set2.random_bytes(random.randrange(10, 100))) msg4 = bob.receive_msg(msg3) exchange_1_valid = alice.verify_echo(msg4) # EchoBot exchange with MitM alice = EchoAlice() bob = EchoBob() eve = EchoEve() secret_msg = set2.random_bytes(random.randrange(10, 100)) msg1 = alice.initiate(p=nist_p, g=nist_g) msg1p = eve.intercept_alice_initiate(msg1) msg2 = bob.receive(msg1p) msg2p = eve.intercept_bob_receive(msg2) msg3 = alice.send_msg(msg2p, secret_msg) msg3p = eve.intercept_alice_send_msg(msg3) msg4 = bob.receive_msg(msg3p) msg4p = eve.intercept_bob_receive_msg(msg4) exchange_2_valid = alice.verify_echo(msg4p) exchange_2_cracked = secret_msg == eve.decrypt_message() # Verify exchanges were valid and Eve was able to obtain the original message assert_true(exchange_1_valid and exchange_2_valid and exchange_2_cracked)
def challenge_44(): # Set up `p`, `q` and `g` parameters p = 0x800000000000000089e1855218a0e7dac38136ffafa72eda7859f2171e25e65eac698c1702578b07dc2a1076da241c76c62d374d8389ea5aeffd3226a0530cc565f3bf6b50929139ebeac04f48c3c84afb796d61e5a4f9a8fda812ab59494232c7d2b4deb50aa18ee9e132bfa85ac4374d7f9091abc3d015efc871a584471bb1 q = 0xf4f47f05794b256174bba6e9b396a7707e563c5b g = 0x5958c9d3898b224b12672c0b98e06c60df923cb8bc999d119458fef538b8fa4046c8db53039db620c094c9fa077ef389b5322a559946a71903f990f1f7e0e025e2d7f7cf494aff1a0470f5b64c36b625a097f1651fe775323556fe00b3608c887892878480e99041be601a62166ca6894bdd41a7054ec89f756ba9fc95302291 # Create new DSA instance dsa = DSA(p, q, g) # Read inputs for this challenge from file messages = get_challenge_44_messages() # Iterate over messages for i, msg1 in enumerate(messages): for msg2 in messages[(i+1):]: # Check if the same `k` was used (implies `r` values are equal) if msg1['r'] == msg2['r']: # Use '9th grade math' to recover `k` a = (msg1['m'] - msg2['m']) % q b1 = (msg1['s'] - msg2['s']) % q b2 = set5.modinv(b1, q) k = (a * b2) % q print('Recovered k: {}'.format(k)) # Recover `x` x = dsa.crack_x(k, msg=msg1['msg'], sig=(msg1['r'], msg1['s'])) print('Recovered x: {}'.format(hashlib.sha1(hex(x)[2:].encode()).hexdigest())) # Verify the found private key `x` is the one we are looking for assert_true(hashlib.sha1(hex(x)[2:].encode()).hexdigest() == 'ca8f6f7c66fa362d40760d135b763eb8527d3d52') return
def challenge_28(): # Generate random key, message key = set2.random_bytes(16) message = set2.random_bytes(random.randrange(128, 1024)) # Compute mac for the generated key and message original_mac = simple_mac(key, message) # Verify changing a byte will change the mac significantly assert_true(mac_tamper(key, message, original_mac))
def challenge_14(): # Intialise the encryption function that will be used key = random_bytes(16) prefix = random_bytes(random.randrange(1, 16)) encrypt = lambda x: encrypt_aes_ecb(prefix + x + magic_string, key) # Brute force the magic string assert_true(bruteforce_ecb_key_2(encrypt) == magic_string)
def challenge_39(): # Get public and private key public, private = set_up_rsa(3) # Generate message msg = random.randrange(2, public[1]) # Encrypt and decrypt ciphertext = encrypt_rsa(msg, public) plaintext = encrypt_rsa(ciphertext, private) # Verify output assert_true(msg == plaintext)
def challenge_24(): # Initialise our new stream cipher stream_cipher_instance = MT19937Cipher(424242) # Generate a random plaintext plaintext = set2.random_bytes(1024) # Encrypt the plaintext, decrypt is ciphertext = stream_cipher_instance.encrypt(plaintext) obtained_plaintext = stream_cipher_instance.decrypt(ciphertext) # Verify the decrypted ciphertext equals our original plaintext assert_true(obtained_plaintext == plaintext)
def challenge_53(): # Define our Merkle Damgard hash function md_hash = lambda x, y, z=False: MerkleDamgard(x, HASH_LENGTH, y, z) # Generate the message we'll attack M = set2.random_bytes(16 * 16) # Generate the hashmap, i.e. the intermediate hashes per block M_hashmap = md_hash(M, b'\x00' * HASH_LENGTH, True) # Compute `k` k = int(math.log2(len(M) // 16)) # Generate expandable message print('Message to preimage has {} blocks (i.e. k = {})'.format( len(M) // 16, k)) print("Generating expandable message... ", end='') expandable_output = expandable_message(k, md_hash) print('done') # Get hash of our final block in the expandable message final_hash = list(expandable_output.values())[-1][0] index = -1 # Find an index that is at equal to or greater than `k` print("Generating bridge... ", end='') while index < k: bridge, bridge_hash = None, None # Check if generated message collides with one of our intermediate hashes while bridge_hash not in M_hashmap[1].keys(): bridge = set2.random_bytes(16) bridge_hash = md_hash(bridge, final_hash) # If it does, that will be our index index = M_hashmap[1][bridge_hash] print("done\nFound collision against block {} of M".format(index)) # The next step is to replace all blocks up until our collision (i.e. the bridge) # This requires some binary math, as sometimes the short message is required, while # sometimes the long one is required. prefix_length = index prefix = b'' for i in range(k, 0, -1): # Compute the length of the long message we're considering q = 2**(i - 1) + 1 # If the prefix length minus the long message length is smaller than the remaining blocks: if prefix_length - q < i - 1: # Use short message prefix_length -= 1 prefix += expandable_output[i][1] else: # Use long message prefix_length -= q prefix += expandable_output[i][2] # Construct the new message constructed_message = prefix + bridge + M[(16 * (index + 1)):] # Verify the constructed message has the same length and the same hash value as our original message assert_true( len(constructed_message) == len(M) and md_hash(constructed_message, b'\x00' * HASH_LENGTH) == md_hash( M, b'\x00' * HASH_LENGTH))
def diffie_helman(p, g): # Generate private keys in p a, b = random.randrange(1, p), random.randrange(1, p) # Generate public keys A, B = modexp(g, a, p), modexp(g, b, p) # Generate shared secret s, s2 = modexp(B, a, p), modexp(A, b, p) # Verify Bob and Alice have the same shared secret assert_true(s == s2) # Return SHA256 of shared secret return hashlib.sha256(long_to_bytes(s)).digest()
def challenge_31(): # Generate random key key = set2.random_bytes(16) filename = b'filename' # Set up web server _thread.start_new_thread(webserver, (key, )) # Generate the hmac we're looking for correct_hmac = hmac_sha1(key, filename).hexdigest() # Try to guess the hmac using our timing attack guessed_hmac = retrieve_valid_hmac(filename) # Verify the guessed hmac is equal to the correct hmac assert_true(correct_hmac == guessed_hmac)
def challenge_32(): # Generate random key key = set2.random_bytes(16) filename = b'filename' # Set up web server, but now with a very small 'insecure_comparison' sleep time _thread.start_new_thread(webserver, (key, 0.01)) # Generate the hmac we're looking for correct_hmac = hmac_sha1(key, filename).hexdigest() # Try to guess the hmac using our timing attack, but now with 10 rounds for each guess guessed_hmac = retrieve_valid_hmac(filename, 10) # Verify the guessed hmac is equal to the correct hmac assert_true(correct_hmac == guessed_hmac)
def challenge_22(): # Get UNIX timestamp timestamp = int(time.time()) # Generate a pseudo-random number using a given timestamp, with delay output, secret_seed = generate_prn_with_delay(int(time.time())) # Now bruteforce the obtained output based on the given timestamp found_seeds = bruteforce_seed(output, timestamp) # Display number of possible seeds found print("{} possible seed{} found".format( len(found_seeds), 's' if len(found_seeds) != 1 else '')) # Verify the used seed is in the list of found (possible) seeds assert_true(secret_seed in found_seeds)
def challenge_18(): # Test given string test_string = "L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ==" assert_true( decrypt_aes_ctr(base64.b64decode(test_string), "YELLOW SUBMARINE", 0) == b'Yo, VIP Let\'s kick it Ice, Ice, baby Ice, Ice, baby ') # Generate random data, nonce and key, test if decrypting after encrypting results in the generated data again. data = set2.random_bytes(64) nonce = random.randint(128, 65536) key = set2.random_bytes(16) assert_true( data == decrypt_aes_ctr(encrypt_aes_ctr(data, key, nonce), key, nonce))
def challenge_26(): # Intialise the encryption parameters nonce = random.randrange(1, 10**10) key = set2.random_bytes(16) # Generate ciphertext for our string ciphertext = bytearray(challenge_26_encryption('-admin-true', key, nonce)) # Replace the 32th character with the XOR of itself with '-' and ';' # Note that AES CTR will XOR this again with the key stream, # hence resulting in ';'. Same strategy for the 38th character. ciphertext[32] = ciphertext[32] ^ ord('-') ^ ord(';') ciphertext[38] = ciphertext[38] ^ ord('-') ^ ord('=') assert_true(challenge_26_admin_check(bytes(ciphertext), key, nonce))
def challenge_17(): # Initialise key key = set2.random_bytes(16) # Call function_1 to get random ciphertext with IV used ciphertext, iv = function_1(key) # For verification purposes, decrypt given ciphertext with key expected = set2.decrypt_aes_cbc(ciphertext, key, iv) print('Expected: {}'.format(expected)) # Initialise oracle function oracle = lambda x: function_2(x, key, iv) # Run oracle attack result = aes_oracle_attack(oracle, ciphertext, iv) print('Found: {}'.format(bytes(result))) # Verify found answer equals what we're expecting assert_true(result == expected) print("")
def challenge_27(): # Intialise the encryption parameters key = set2.random_bytes(16) # Generate ciphertext for our string ciphertext = challenge_27_encryption('whatevah', key) # Modify the ciphertext such that it is equal to c_1 + 0 + c_2 ciphertext = ciphertext[0:16] + b'\x00' * 16 + ciphertext[0:16] try: # Verify this modified ciphertext will now parse 'admin':'true' assert_true(not challenge_27_check_decrypt(ciphertext, key)) except ValueError as error: # Extract the deciphered text from the exception plaintext = error.args[1] # XOR p_1 with p_3 derived_key = bytes( [x ^ y for x, y in zip(plaintext[0:16], plaintext[32:48])]) # Verify the derived key equals the original key assert_true(derived_key == key)
def challenge_48(): # Set up new RSA instance, this time with bigger key length pub, priv = set5.set_up_rsa(e=3, keysize=768) # Prepare message message = pad_PKCS(b'I don\'t know, Marge. Trying is the first step towards failure - Homer Simpson', k=(pub[1].bit_length() + 7) // 8) # Get ciphertext using generated RSA instance ciphertext = set5.encrypt_rsa(set5.bytes_to_int(message), pub) # Set up our Oracle oracle = lambda x: rsa_oracle_02(x, priv) assert oracle(ciphertext) # Perform the actual attack: set up bleichenbacher98 instance bb98 = bleichenbacher98(ciphertext, pub, oracle) # Run the attack found_message = bb98.solve() print("Found message:", found_message) # Verify the found message equals our original plaintext assert_true(found_message == message)
def challenge_47(): # Set up new RSA instance pub, priv = set5.set_up_rsa(e=3, keysize=256) # Prepare message message = pad_PKCS(b'kick it, CC', k=(pub[1].bit_length() + 7) // 8) # Get ciphertext using generated RSA instance ciphertext = set5.encrypt_rsa(set5.bytes_to_int(message), pub) # Set up our Oracle oracle = lambda x: rsa_oracle_02(x, priv) assert oracle(ciphertext) # Perform the actual attack: set up bleichenbacher98 instance bb98 = bleichenbacher98(ciphertext, pub, oracle) # Run the attack found_message = bb98.solve() print("Found message:", found_message) # Verify the found message equals our original plaintext assert_true(found_message == message)
def challenge_54(): # Set parameters, define hash function k = 4 # 2^4 = 16 md_hash = lambda x, y: MerkleDamgard(x, 2, y) START_IV = b'\x00' * 16 # Generate `2^k` random messages with their hashes, then add them as leaves to the hash/message tree hash_tree, msg_tree = [[]], [[]] for _ in range(2**k): msg = set2.random_bytes(16) msg_tree[0].append(msg) hash_tree[0].append(md_hash(msg, START_IV)) # Generate full hash/message tree print('k = {}'.format(k)) with tqdm(desc="Generating hash tree", total=(2**k) - 1) as progress: hash_tree, msg_tree = generate_hash_tree(hash_tree, msg_tree, md_hash, progress) # Make claim based on the root of the hash tree hash_value = hash_tree[-1][0] print('> I hereby claim the hash will be equal to {}!'.format(hash_value)) # Now, _after_ we have made our claim, craft our message with the match results msg = b'Ajax-PSV=3-0; Feyenoord-RKC=0-15' # Generate hash of our message msg_hash = md_hash(msg, START_IV) # Our crafted message starts with the original message crafted_msg = msg # Find a collision between our message's hash and one of the leaves in the hash tree glue, glue_hash = None, None while glue_hash not in hash_tree[0]: glue = set2.random_bytes(16) glue_hash = md_hash(glue, msg_hash) # Append the glue to our crafted message crafted_msg += glue # Find the index of the leaf we found a collision against index = hash_tree[0].index(glue_hash) # Now find the remaining `k` blocks that will eventually hash to `hash_value` for level in range(1, k + 1): index = index // 2 crafted_msg += msg_tree[level][index] # Verify our crafted message hashes to the hash value we claimed it would have assert_true(md_hash(crafted_msg, START_IV) == hash_value)
def challenge_42(): # Create new RSA Sign/Verify instance m = RsaSignVerify() print('> Signed message') # Part 1: try to verify a valid signature signature = m.sign(b'Hello world') assert m.verify(b'Hello world', signature) # Part 2: forge a signature print('\n> Forged message') # Find digest for message to forge forged_message = b'hi mom' digest = hashlib.sha1(forged_message).digest() # Set the contents of the (fake) message to forge fake_message = b'\x00\x01\xff\x00' + digest fake_message = set5.bytes_to_int(fake_message + (b'\x00' * (128 - len(fake_message)))) # The actual trick: find the cube root fake_signed_message = cuberoot(fake_message) # Check that the forged message passes verification assert_true(m.verify(forged_message, fake_signed_message))
def challenge_23(): # Create prng instance to clone prng_instance = MT19937(13371337) generated_values = [] found_mt_values = [] # Generate the first N numbers, and try to find the underlying values in the internal MT array using our untemper function for _ in range(prng_instance.N): generated_values.append(prng_instance.generate()) found_mt_values.append(untemper(generated_values[-1])) # Create a dummy MT19937 instance clone_prng_instance = MT19937(0) # Set the internal MT array, reset index value to 0 clone_prng_instance.mt = found_mt_values clone_prng_instance.index = 0 # Using this cloned MT19937 instance, generate the first N values again clone_generated_numbers = [ clone_prng_instance.generate() for _ in range(MT19937.N) ] # Verify the results assert_true(generated_values == clone_generated_numbers)
def challenge_51(): # Prepare a Stream Cipher compression oracle and an AES-CBC compression oracle stream_oracle = lambda msg: compression_oracle( msg, lambda x: set3.encrypt_aes_ctr( x, key=set2.random_bytes(16), nonce=random.randint(2**8, 2**16))) cbc_oracle = lambda msg: compression_oracle( msg, lambda x: set2.encrypt_aes_cbc( x, key=set2.random_bytes(16), iv=set2.random_bytes(16))) # Define the base text base = 'POST / HTTP/1.1\r\nHost: hapless.com\r\nCookie: sessionid=' # Find the tokens using the Stream Cipher print('Stream Cipher: ', end='') assert_true(find_token(base, oracle=stream_oracle)[len(base):] == token) # AES-CBC is more challenging, as it works with blocks. # To detect a successful guess, we'll have to add padding which will make 'wrong' guesses one block longer than the correct one print('AES-CBC: ', end='') assert_true(find_token(base, oracle=cbc_oracle)[len(base):] == token)
def challenge_25(): key = set2.random_bytes(16) nonce = random.randrange(1, 10**10) # Get plaintext with open('inputs/25.txt') as file: contents = base64.b64decode(file.read()) plaintext = set2.pkcs7_remove_padding( set1.decrypt_aes_ecb(contents, 'YELLOW SUBMARINE')) # Generate ciphertext ciphertext = set3.encrypt_aes_ctr(plaintext, key, nonce) # Create vulnerable function def vulnerable_ctr_stream_edit_function(ciphertext, offset, newtext): return edit_ctr_stream(ciphertext, key, nonce, offset, newtext) # Find the plaintext using the above function obtained_plaintext = find_ctr_plaintext( ciphertext, vulnerable_ctr_stream_edit_function) # Verify found plaintext matches with the original plaintext assert_true(obtained_plaintext == plaintext)
def challenge_16(): # Intialise the encryption parameters iv = random_bytes(16) key = random_bytes(16) # Generate ciphertext for our string ciphertext = bytearray(challenge_16_encryption('-admin-true', key, iv)) # Replace the 16th character with the XOR of itself with '-' and ';' # Note that AES CBC will XOR this again with the 16th character and '-', # hence resulting in ';'. Same strategy for the 22nd character. ciphertext[16] = set1.XOR(set1.XOR(bytes([ciphertext[16]]), b'-'), b';')[0] ciphertext[22] = set1.XOR(set1.XOR(bytes([ciphertext[22]]), b'-'), b'=')[0] try: # Verify this modified ciphertext will now parse 'admin':'true' assert_true(challenge_16_admin_check(bytes(ciphertext), key, iv)) except ValueError as error: # This may happen if the second block outputs an '=' or '&' by accident print('Invalid string formed, trying again') challenge_16()
def challenge_45(): # Set up `p` and `q` parameters p = 0x800000000000000089e1855218a0e7dac38136ffafa72eda7859f2171e25e65eac698c1702578b07dc2a1076da241c76c62d374d8389ea5aeffd3226a0530cc565f3bf6b50929139ebeac04f48c3c84afb796d61e5a4f9a8fda812ab59494232c7d2b4deb50aa18ee9e132bfa85ac4374d7f9091abc3d015efc871a584471bb1 q = 0xf4f47f05794b256174bba6e9b396a7707e563c5b # Create new DSA instance with `g` = `p` + 1 dsa = DSA(p, q, g=p+1) x, y = dsa.generate_keypair() # Part 1: try to verify a valid signature message = b"Hello world!" sig, _ = dsa.sign(message, x) assert dsa.verify(message, sig, y) # Part 2: Try to obtain the magic signature that will verify every message # Let's pick z=1 to keep things simple r2 = (y % p) % q s2 = r2 % q sig2 = (r2, s2) # Verify two arbitrary strings against the same signature assert_true(dsa.verify(b"Hello, world", sig2, y) and dsa.verify(b"Goodbye, world", sig2, y))
def challenge_29(): # Generate random key of random number of bytes key = set2.random_bytes(random.randrange(4, 40)) # Prepare validation function def validate_mac(message, digest): return simple_mac(key, message, False).hexdigest() == digest.hexdigest() # Prepare message to sign msg = b"comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon" # Generate genuine mac of the message original_mac = simple_mac(key, msg, False) # Obtain a, b, c, d and e abcde = original_mac._digest() # Prepare string to inject string_to_inject = b";admin=true" # Create a forged mac: # - The forged length is {msg} and it's padding (hence a multiple of 64), plus the length of our injected string # - We feed the SHA-1 instance the [a-e] parameters obtained from the original mac forged_mac = external.slowsha.SHA1( string_to_inject, math.ceil(len(msg) / 64) * 64 + len(string_to_inject), abcde) # To figure out what message we actually signed, we need to figure out what the padding was of the original mac # Because we don't know the key, we have to guess the key length for key_length_guess in range(4, 41): # Compose the candidate message based on the key length guess signed_msg_guess = msg + get_sha1_padding(b'\x00' * key_length_guess + msg) + string_to_inject # Verify the validity of the guessed message with the forged mac if validate_mac(signed_msg_guess, forged_mac): print( "Successfully forged message - key used had length {}".format( key_length_guess)) assert_true(True) return raise Exception("Could not forge MAC")
def challenge_49(): # Define User IDs Alice, Bob, Carol, Eve = 100, 101, 102, 600 # Define fixed IV fixed_iv = b'\x00' * 16 # Sample for Alice print('Sample for Alice') valid_message_alice = generate_transaction(Alice, fixed_iv, [{ 'to_id': Bob, 'amount': 100 }, { 'to_id': Carol, 'amount': 500 }]) assert (validate_transaction(valid_message_alice)) # Generate transaction based on own account (transferring 1M from own account to own account) valid_message_eve = generate_transaction(Eve, fixed_iv, [{ 'to_id': Eve, 'amount': 1000000 }]) print('\nValid message (not sent to server): {}'.format(valid_message_eve)) # Forge transaction by changing the 'from' account to Alice's forged_first_block = 'from={}&tx_list'.format(Alice).encode() # As we can control the IV, XOR new string with original string (and the original, fixed IV) to obtain the IV that will make our forged message valid custom_iv = xor_string( fixed_iv, xor_string(valid_message_eve[:16], forged_first_block)) # Construct message forged_message = forged_first_block + valid_message_eve[ 16:-32] + custom_iv + valid_message_eve[-16:] print('Forged message: {}'.format(forged_message)) print('Send to server...') # Validate forged message assert_true(validate_transaction(forged_message))