1
\$\begingroup\$

I made a simple password cracker using Python. But it's extremely slow. Here is my code:

import itertools
import string
import sys
import socket

def connection(ip, user, passw, port):
    s = socket.socket()
    s.connect((ip, int(port)))
    data = s.recv(1024)
    s.send(('USER ' + user + '\r\n').encode())
    data = s.recv(1024)
    s.send(('PASS ' + passw + '\r\n').encode())
    data = s.recv(1024)
    s.send(('quit\r\n').encode())
    s.close()
    return data
def crack(ip, user, port):
    chars = string.digits + string.ascii_letters
    for password_length in range(1, 9):
        for guess in itertools.product(chars, repeat = password_length):
            guess = ''.join(guess)
            p = connection(ip, user, guess, port)
            if '230'.encode() in p:
                print('Username : ' + user + '\nPassword : ' + guess)
                sys.exit(1)
if len(sys.argv) != 4:
    print('Usage: ./passcracker.py <IP> <Username> <Port>')
    sys.exit(1)
crack(sys.argv[1], sys.argv[2], sys.argv[3])

I want to make it faster. Also if there is any wrong part in code, please tell me.

\$\endgroup\$
  • 2
    \$\begingroup\$ The slowness is very likely caused primarily by needing to connect to some server, sending data over the network, the server taking some time for the password check (and maybe explicitly slowing down on multiple attempts) etc. All these things are out of control of your program, i.e. caused by infrastructure you don't control. At most you can try to run multiple of such programs in parallel, each with a different sets of passwords to try. \$\endgroup\$ – Steffen Ullrich Jan 12 at 9:15
  • 1
    \$\begingroup\$ @Graipher yeah it's a copy-paste error thanks \$\endgroup\$ – Akın Oktay ATALAY Jan 12 at 10:05
2
\$\begingroup\$

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):

enter image description here

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.

\$\endgroup\$
  • \$\begingroup\$ Thanks. But I have a question. Why do you used '@' in some points? Like @timeit or @wraps(func) \$\endgroup\$ – Akın Oktay ATALAY Jan 12 at 13:18
  • \$\begingroup\$ @AkınOktayATALAY: That is the way decorators are used. Have a look e.g. here: programiz.com/python-programming/decorator \$\endgroup\$ – Graipher Jan 12 at 13:37
  • \$\begingroup\$ The decorator doesn't work I get output: TypeError: 'NoneType' object is not callable \$\endgroup\$ – Akın Oktay ATALAY Jan 12 at 13:58
  • \$\begingroup\$ @AkınOktayATALAY Are you copying everything? Does it work with the dummy function f? In which line does the problem occur? It does work on my machine... \$\endgroup\$ – Graipher Jan 12 at 14:14

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.