Python Making Bruteforce Password Crack Faster
As has been noted in the comments, you should try to figure out what part of the code is slow. Is it the connection to the server or does your program just have to try many passwords and that takes so long?



The former can be measured by decorating connection with a decorator that records the time it took to run the function:

import time
from functools import wraps

def timeit(func):
    func.mean_time = [0]
    func.k = [0]
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        ret = func(*args, **kwargs)
        t = time.perf_counter() - start

        # update average
        func.k[0] += 1
        func.mean_time[0] += (t - func.mean_time[0]) / func.k[0]

        print(f"{func.__name__} took {t} s (Average: {func.mean_time[0]} s)")
        return ret
    return wrapper


Which you can use like this in general:

@timeit
def f():
    time.sleep(0.1)

for _ in range(10):
    f()
# f took 0.1002191620063968 s (Average: 0.1002191620063968 s)
# f took 0.10021526199852815 s (Average: 0.10021721200246247 s)
# f took 0.10016683799767634 s (Average: 0.10020042066753376 s)
# f took 0.10014399800274987 s (Average: 0.10018631500133779 s)
# f took 0.10016678299871273 s (Average: 0.10018240860081278 s)
# f took 0.10017002299719024 s (Average: 0.10018034433354235 s)
# f took 0.10020436099875951 s (Average: 0.10018377528571623 s)
# f took 0.1001491690039984 s (Average: 0.1001794495005015 s)
# f took 0.10017034399788827 s (Average: 0.10017843777798892 s)
# f took 0.10020105999865336 s (Average: 0.10018070000005536 s)


And here specifically:

@timeit
def connect(ip, user, passw, port):
    ...


Note that this will slow down the overall execution time a bit (since stuff needs to be done in addition), but you do learn if the connect is the bottleneck (and you can always remove the timing again later).



To find out if it is just the number of permutations, I would add some debug prints. I would also factor out the generating of the passwords from trying them further:

def brute_force_n(chars, password_length):
    start = time.perf_counter()
    for i, guess in enumerate(itertools.product(chars, repeat=password_length)):
        yield ''.join(guess)
    print(f"Tried all {i + 1} permutations of length {password_length}.")
    print(f"It took {time.perf_counter() - start} s.")

def brute_force(max_length=8):
    chars = string.digits + string.ascii_letters
    for password_length in range(1, max_length + 1):
        yield from brute_force_n(chars, password_length)

def crack(ip, user, port):
    for guess in brute_force():
        p = connection(ip, user, guess, port)
        if '230'.encode() in p:
            print('Username : ' + user + '\nPassword : ' + guess)
            sys.exit(1)


When testing this you will quickly discover that there are many permutations to try and even when doing nothing with them, this takes quite some time:

for _ in brute_force(5):
    pass  # do nothing with it

# Tried all 62 permutations of length 1.
# It took 3.321799886180088e-05 s.
# Tried all 3844 permutations of length 2.
# It took 0.0009744890048750676 s.
# Tried all 238328 permutations of length 3.
# It took 0.06495958699815674 s.
# Tried all 14776336 permutations of length 4.
# It took 4.06446365499869 s.
# Tried all 916132832 permutations of length 5.
# It took 310.80436263100273 s.


I stopped at length 5, you want to go to length 8. As you can see in this plot, the time rises very quickly (note the logarithmic y-axis):



Extrapolating this to password_length = 8, it would take about 536 days just to generate all combinations of that length.



The real solution to this problem is that you need to use some more information/a more clever tactic. A common method is to try words in a dictionary (and then words in a dictionary with numbers at the end, with known common replacements, etc).

Passwords are still used today because it is very hard to guess a (random) password of sufficient length.