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()
Exemple #2
0
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
Exemple #3
0
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
Exemple #6
0
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()
Exemple #7
0
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