5
\$\begingroup\$

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:
 next_guess.append("!")
 return next_guess
def hash_password(pwrd):
 # Generates hash of original password
 try:
 pwrd = pwrd.encode("UTF-8")
 password = hashlib.sha256()
 password.update(pwrd)
 return password.hexdigest()
 except:
 pass
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}'")
 list_cracked.append(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"):
 try:
 password = password.strip()
 hash = hash_password(password)
 except:
 pass
 if hash in secure_password:
 # print(f"Another password has been cracked: {password}")
 list_cracked.append(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}")
 list_cracked.append(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:
 secure_password.add(hash_password(password))
 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")
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Nov 16, 2020 at 16:46
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

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
 break
 
 else:
 guess[i] = ORD_BANG
 
 else:
 guess.append(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'
answered Nov 17, 2020 at 0:35
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.