def check_block_delta_candidate(block_delta_i): with cmsh.Model() as model: hf: hfa.md4 = hfa.resolve(ALGO) # Ensure our delta is valid. block_delta = model.to_vec(block_delta_i, hf.block_size) # First construct two blocks with the given delta. block_1 = model.vec(hf.block_size) block_2 = model.vec(hf.block_size) model.add_assert((block_1 ^ block_2) == block_delta) # Assume we have any possible iv. This is safe even in the case # that iv_1 == iv_2 and our delta doesn't align with the blocks # under test (3, 11, 7, or 15), because we later calculate the # last possible round where we could have a difference and still # collide. iv_1 = model.vec(hf.state_size) iv_2 = model.vec(hf.state_size) # Compute the last 4 rounds and check if this is a viable candidate # (that we can generate a collision with). output_1s, _ = hf.eval(model, block_1, iv=iv_1, rounds=range(hf.rounds - 4, hf.rounds)) output_2s, _ = hf.eval(model, block_2, iv=iv_2, rounds=range(hf.rounds - 4, hf.rounds)) output_1 = model.join_vec(output_1s) output_2 = model.join_vec(output_2s) model.add_assert(output_1 == output_2) assert model.solve()
def typed_cols(db, algo, columns, binary=False): algo = hfa.resolve(algo) result = [] for column in columns: column_type = db.to_type(algo.type(column), binary=binary) result.append(f"{column} {column_type}") return result
def get_columns(db, algo, prefix: Optional[str] = None) -> List[str]: """ Create the columns for a single instance of a hash function. This returns the initialization vector, the block, all the rounds, and the result. """ algo = hfa.resolve(algo) cols = algo.columns() return hfu.prefix_list(cols, prefix)
def main(): # Grab a hash algorithm instance so we can initialize our database. hf: hfa.md4 = hfa.resolve(ALGO) # Initialize our database to generate collisions and store intermediate # round results. db = hfd.Database("~/hf/round.db") hfd.create_table(db, hf) # User-supplied block differential. block_delta = 1 << 511 # First check that the given block delta can produce collisions in the # last few rounds. check_block_delta_candidate(block_delta) # Then compute the exact final target round; after this round, the blocks # have no difference so we need to have reached zero state delta by then. delta_indices, last_round = last_possible_round(block_delta) print(delta_indices, last_round) # These constraints allow user-supplied assumptions in parallel to # discovered constraints. iv_delta_constraints = [None] * hf.rounds iv_h1_value_constraints = [None] * hf.rounds iv_h2_value_constraints = [None] * hf.rounds # User-supplied assumption: The first round's IV is the default, and thus # thus the delta is 0. iv_delta_constraints[0] = 0 iv_h1_value_constraints[0] = hf.default_state iv_h2_value_constraints[0] = hf.default_state # Load existing knowledge from the database. print("Loading existing pieces from the database...") known_pieces = organize_pieces(hf, load_known_pieces(db, hf, block_delta)) print(f"Got {piece_count(known_pieces)} pieces") # Create a single generator instance for creating arguments to solve for # valid differential pieces. piece_generator = piece_args_gen(hf, block_delta, iv_delta_constraints, iv_h1_value_constraints, iv_h2_value_constraints) # Assume the user will hit Ctrl+C when we've run for long enough. while True: # Start by solving for new pieces that could augment our new_process_pieces = hfu.parallel_run(find_single_piece, piece_generator, items_to_process=100) for process_pieces in new_process_pieces: for piece in process_pieces: print(piece) added = add_piece(known_pieces, piece) if added: store_piece(piece)
def last_possible_round(block_delta_i): with cmsh.Model() as model: hf: hfa.md4 = hfa.resolve(ALGO) # Ensure our delta is valid and split it into block-sized chunks. block_delta = model.to_vec(block_delta_i, hf.block_size) block_deltas = model.split_vec(block_delta, hf.block_size // hf.blocks) last_round = 0 delta_indices = set() for round_num, block_index in enumerate(hf.block_schedule): if int(block_deltas[block_index]) != 0: delta_indices.add(block_index) last_round = round_num assert last_round != 0 return tuple(delta_indices), last_round
def test_save_collision(): with cmsh.Model() as model: hf = hfa.resolve('md4') k1 = "839c7a4d7a92cb5678a5d5b9eea5a7573c8a74deb366c3dc20a083b69f5d2a3bb3719dc69891e9f95e809fd7e8b23ba6318edd45e51fe39708bf9427e9c3e8b9" k2 = "839c7a4d7a92cbd678a5d529eea5a7573c8a74deb366c3dc20a083b69f5d2a3bb3719dc69891e9f95e809fd7e8b23ba6318edc45e51fe39708bf9427e9c3e8b9" assert k1 != k2 known_shaped_1 = list( map(lambda x: model.to_vector(x, width=32), hfa.split_hex(k1, 8))) known_shaped_2 = list( map(lambda x: model.to_vector(x, width=32), hfa.split_hex(k2, 8))) block_1 = model.vec(hf.block_size) block_2 = model.vec(hf.block_size) output_1s, rounds_1s = hf.compute(model, block_1) output_2s, rounds_2s = hf.compute(model, block_2) output_1 = model.join_vec(output_1s) output_2 = model.join_vec(output_2s) rounds_1 = model.join_vec(rounds_1s) rounds_2 = model.join_vec(rounds_2s) model.add_assert(block_1 < block_2) model.add_assert(block_1 == model.join_vec(known_shaped_1)) model.add_assert(block_2 == model.join_vec(known_shaped_2)) model.add_assert(output_1 == output_2) assert model.solve() assert int(output_1) == int(output_2) db = hfd.Database(":memory:") default_state = model.join_vec( map(lambda x: model.to_vec(x, 32), hf.default_state)) hfd.create_table(db, hf) hfd.save_collision(db, hf, model, block_1, default_state, output_1, rounds_1, block_2, default_state, output_2, rounds_2) db.close()
def find_differential(num_block_bits, state_differential): with cmsh.Model() as model: model.solver.config_timeout(30) hf: hfa.md4 = hfa.resolve('md4') raw_block_1 = model.vec(32) raw_block_2 = model.vec(32) model.add_assert((raw_block_1 ^ raw_block_2).bit_sum() == num_block_bits) block_1 = [ raw_block_1 ] * (hf.block_size // 32) block_2 = [ raw_block_1 ] * (hf.block_size // 32) for round_index in range(0, hf.rounds): iv_1 = model.vec(hf.state_size) iv_1s = model.split_vec(iv_1, hf.int_size) iv_2 = model.vec(hf.state_size) iv_2s = model.split_vec(iv_2, hf.int_size) model.add_assert((iv_1s[0] ^ iv_2s[0]) == state_differential) for index, state_1 in enumerate(iv_1s[1:], start=1): model.add_assert(state_1 == iv_2s[index]) output_1s, _ = hf.eval(model, block_1, iv=iv_1s, rounds=[round_index]) output_2s, _ = hf.eval(model, block_2, iv=iv_2s, rounds=[round_index]) output_1 = model.join_vec(output_1s) output_2 = model.join_vec(output_2s) model.add_assert(output_1 == output_2) sat = model.solve() if sat is True: print(f"{state_differential} - SAT") elif sat is None: print(f"{state_differential} - timeout")
def find_single_piece(round_num, block_delta_i, iv_delta_constraint, iv_h1_value_constraint, iv_h2_value_constraint): """ Without any regard for whether this fits together, find a bunch of individual pieces (single rounds) we can try and piece together later. We still need to comply with the input constraints (block delta, iv_delta, and fixed iv_values) though. """ with cmsh.Model() as model: hf: hfa.md4 = hfa.resolve(ALGO) # Use any iv and refine according to input constraints. iv_1 = model.vec(hf.state_size) iv_2 = model.vec(hf.state_size) if iv_delta_constraint: model.add_assert( (iv_1 ^ iv_2) == model.to_vec(iv_delta_constraint, hf.state_size)) if iv_h1_value_constraint: constraint = model.join_vec( map(lambda v: model.to_vec(v, hf.int_size), iv_h1_value_constraint)) model.add_assert(iv_1 == constraint) if iv_h2_value_constraint: constraint = model.join_vec( map(lambda v: model.to_vec(v, hf.int_size), iv_h2_value_constraint)) model.add_assert(iv_2 == constraint) # Create a block according to the specified block delta. block_delta = model.to_vec(block_delta_i, hf.block_size) block_1 = model.vec(hf.block_size) block_2 = model.vec(hf.block_size) model.add_assert((block_1 ^ block_2) == block_delta) # Finally, compute a single round. output_1s, rounds_1s = hf.eval(model, block_1, iv=iv_1, rounds=[round_num]) output_2s, rounds_2s = hf.eval(model, block_2, iv=iv_2, rounds=[round_num]) output_1 = model.join_vec(output_1s) output_2 = model.join_vec(output_2s) round_1 = model.join_vec(rounds_1s) round_2 = model.join_vec(rounds_2s) _ = iv_1 ^ iv_2 _ = block_1 ^ block_2 _ = output_1 ^ output_2 model.add_assert((round_1 ^ round_2).bit_sum() <= 5) assert len(round_1) == hf.int_size assert len(round_2) == hf.int_size discovered = [] while model.solve(max_time=1) is True: if not model.sat: continue discovered.append({ 'round': round_num, 'h1_state': hex(int(iv_1))[2:], 'h2_state': hex(int(iv_2))[2:], 'd_state': hex(int(iv_1) ^ int(iv_2))[2:], 'h1_block': hex(int(block_1))[2:], 'h2_block': hex(int(block_2))[2:], 'd_block': hex(int(block_1) ^ int(block_2))[2:], 'h1_output': hex(int(output_1))[2:], 'h2_output': hex(int(output_2))[2:], 'd_output': hex(int(output_1) ^ int(output_2))[2:], }) negated = model.negate_solution(round_1 ^ round_2) model.add_assert(negated) if len(discovered) >= 128: # Arbitrary cutoff to prevent individual tasks from running # too long. break return discovered