I have updated my previous code with the suggestions. I have also implemented concurrency to speed up the results.

I'd be interested for comments on a more efficient use on concurrency / parallel processing / hyper-threading

import concurrent.futures
import hashlib
import time

def make_next_guess(guess):
    carry = 0
    next_guess = guess
    for i, char in enumerate(next_guess):
        if ord(char) >= ord("z"):
            carry = 1
            next_guess[i] = "!"
        elif ord(char) < ord("z"):
            next_guess[i] = chr(ord(char) + 1)
            carry = 0
        if carry == 0: break
    if carry:
    return next_guess

def hash_password(pwrd):
    # Generates hash of original password
        pwrd = pwrd.encode("UTF-8")
        password = hashlib.sha256()
        return password.hexdigest()

def find_password(secure_password, guess, integer):
    list_cracked = []
    # Brute force to find original password
    for _ in range(90 ** integer):  # password maximum length 14 and there are 58 characters that can be used
        return_password = "".join(guess)
        if hash_password(return_password) in secure_password:
            # print(f"Another password has been cracked: '{return_password}'")
        guess = make_next_guess(guess)
    return f"Finished cracking all passwords of length {integer}", list_cracked, integer

def rainbow_table_check(secure_password):
    global hash
    list_cracked = []
    for password in open("Rainbow.txt", "r", encoding="utf8"):
            password = password.strip()
            hash = hash_password(password)
        if hash in secure_password:
            # print(f"Another password has been cracked: {password}")
    return "Rainbow attack complete - passwords: ", list_cracked, "Rainbow"

def dic_attack(secure_password):
    list_cracked = []
    for password in open("Dic.txt", "r", encoding="utf8"):
        password = password.strip()
        lst = [password.lower(), password.upper(), password.title()]
        for password in lst:
            hash = hash_password(password)
            if hash in secure_password:
                # print(f"Another password has been cracked: {password}")
    return "Dictionary attack complete - passwords: ", list_cracked, "Dictionary"

if __name__ == "__main__":
    all_passwords = []
    start = time.time()
    secure_password = set()
    print("Attempting to crack passwords....")
    password_list = ["ABd", "Abc", "lpo", "J*&", "Njl", "!!!!", "Aqz", "Apple", "Cake", "Biden", "password1"]
    for password in password_list:
    with concurrent.futures.ProcessPoolExecutor() as executor:
        results = [executor.submit(dic_attack, secure_password),
                   executor.submit(rainbow_table_check, secure_password),
                   executor.submit(find_password, secure_password, ['!'], 1),
                   executor.submit(find_password, secure_password, ['!', '!'], 2),
                   executor.submit(find_password, secure_password, ['!', '!', '!'], 3),
                   executor.submit(find_password, secure_password, ['!', '!', '!', '!'], 4),
                   executor.submit(find_password, secure_password, ['!', '!', '!', '!', '!'], 5)]
        for f in concurrent.futures.as_completed(results):
            message, cracked, method = f.result()
            time_run = f"{round((time.time() - start) // 60)} min {round((time.time() - start) % 60)} sec"
            print(f"{message} : {cracked} - {time_run}")
            all_passwords += cracked
    print(f"Complete list of cracked passwords: {set(tuple(all_passwords))}")
    print(f"This operation took: {round((time.time() - start) // 60)} min {round((time.time() - start) % 60)} sec")

1 Answer 1


Two observations:

The methods in hashlib expect to receive bytes. In make_next_guess(), use a bytearray instead of a string. That avoids the calls to ''.join(), ord(), ''.encode('UTF-8'), etc. Better yet, make the function a generator that yields the guesses.

Using the else clause on the for i, byte in enumerate(guess): loop simplifies the logic a bit. When the loop is finishes, the else clause is executed. However, a break skips the else clause. Here, if the loop doesn't find any bytes to increase, the else clause add another byte to the length of the guess.

Something like:

def generate_guesses(start):
    ORD_Z = ord('z')
    ORD_BANG = ord("!")
    guess = start[:]
    while True:
        yield guess
        for i, byte in enumerate(guess):
            if byte < ORD_Z:
                guess[i] += 1
                guess[i] = ORD_BANG

Called like:

for guess in generate_guesses(bytearray(b'xzzzz')):
    ... do something with the guess ...

Possibly, add a stop or count argument that tells when the generator should stop or how many guesses it should generate. Just change the while True: line to check the stop condition.

Second observation is that the last job submitted to the Pool, is 90 times more work than the job before it. Indeed, it is more work than the previous 4 jobs combined (maybe all the other jobs). As a result, you end up with the other jobs finishing sooner and one job running on one processor core for a long time. Try splitting the jobs up into more equal sized chunks to keep all the processor cores busy. For example, the jobs could work on equal sized chunks of the password search space:

'!'    to 'zzz' 
'!!!!' to 'zzz!'
'!!!"' to 'zzz"'
'!!!#' to 'zzz#' these are all chunks of 729k (90*90*90) guesses
'!!!z' to 'zzzz'
  • \$\begingroup\$ That is a good idea. Thank you \$\endgroup\$
    – EML
    Nov 17, 2020 at 13:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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