5
\$\begingroup\$

This program only works on Python version equal to or higher than 3.6. Credits to @Graipher for helping with the previous version. Some improvements:

  • Importing the program and using it in another file is easier;
  • Calling the program from the command line is easier;
  • Optional debug mode greatly improves performance (up to 8 times faster);
  • Multiple algorithms available (sha1, sha256, sha3_256 etc.)
  • Code is more readable.

pycrack.py: (hey, I had to think of something)

import hashlib
from sys import argv
from time import time
from itertools import product
from string import ascii_lowercase, ascii_uppercase, digits
colors = {"red":"033円[91m", 
 "green":"033円[92m", 
 "none":"033円[00m"
 }
def get_charset(arg_charset):
 charset = ""
 charsets = {"L":ascii_lowercase,
 "U":ascii_uppercase,
 "D":digits
 }
 for key in arg_charset: 
 # Supply charset argument as list or str if imported
 # Only str accepted from command line prompt.
 charset += charsets[key]
 return charset
def get_algorithm(arg_algo):
 algorithms = {"md5":hashlib.md5,
 "sha1":hashlib.sha1,
 "sha224":hashlib.sha224,
 "sha256":hashlib.sha256,
 "sha384":hashlib.sha384,
 "sha512":hashlib.sha512,
 "sha3_224":hashlib.sha3_224,
 "sha3_256":hashlib.sha3_256,
 "sha3_384":hashlib.sha3_384,
 "sha3_512":hashlib.sha3_512,
 }
 return algorithms[arg_algo]
def timer(func):
 def wrapper(*args, **kwargs):
 timer_start = time()
 timer_return = func(*args, **kwargs)
 timer_diff = int(time()-timer_start)
 print(f"{colors['green']}Bruteforce done{colors['none']}")
 print("Statistics")
 print("_________________________________________")
 print("Calculation time: {}{}{} seconds".format(
 colors['green'],
 timer_diff,
 colors['none']))
 print("_________________________________________")
 return timer_return
 return wrapper
@timer
def bruteforce(hash_, charset, min_length, max_length, algo, debug):
 for length in range(int(min_length), int(max_length) + 1):
 for attempt in product(charset, repeat=length):
 hashed = "".join(attempt).encode("utf-8") 
 # Calling this hashed because otherwise statistics would
 # show - found b"<<original>>" - which is ugly
 hashed = algo(hashed).hexdigest()
 if hashed != hash_:
 if debug:
 print(f"{colors['red']}{''.join(attempt)}{colors['none']}")
 else:
 if debug:
 print(f"{colors['green']}{''.join(attempt)}{colors['none']}")
 return "".join(attempt)
def main():
 hash__, charset_, min_length_, max_length_, algo_, debug_ = argv[1:7]
 charset = get_charset(charset_)
 algo = get_algorithm(algo_)
 res = bruteforce(hash__, charset, min_length_, max_length_, algo, debug_)
 if res is None:
 print(f"{colors['red']}No matches found.{colors['none']}")
 print(colors['none'])
if __name__ == "__main__":
 print("\n"*90)
 main()

And here's an example implementation in another file:

import pycrack
hash_ = "d04b98f48e8f8bcc15c6ae5ac050801cd6dcfd428fb5f9e65c4e16e7807340fa"
charset = pycrack.get_charset("LUD")
min_length = 1
max_length = 5
algo = pycrack.get_algorithm("sha256")
print("\n"*80)
r = pycrack.bruteforce(hash_, charset, min_length, max_length, algo, True)
if r is None:
 print("No matches.")
else:
 print(f"Match: 033円[92m{r}")
print("033円[00m")
print("\n"*10)

Results (debug=False):

Bruteforce done
Statistics
_________________________________________
Calculation time: 3 seconds
_________________________________________
Hash: d04b98f48e8f8bcc15c6ae5ac050801cd6dcfd428fb5f9e65c4e16e7807340fa
Match: hash

Results (debug=True):

hasd
hase
hasf
hasg
hash
Bruteforce done
Statistics
_________________________________________
Calculation time: 24 seconds
_________________________________________
Hash: d04b98f48e8f8bcc15c6ae5ac050801cd6dcfd428fb5f9e65c4e16e7807340fa
Match: hash

Now, I still have the feeling there's a lot issues with my code.

  1. In the get_algorithm() function, if one or more of the hashing algorithms aren't available on the user's system, the program will raise an AttributeError. Is there any way to efficiently check the available algorithms and add them to algorithms? I tried using list comprehensions and hashlib.algorithms_available, but couldn't figure it out.
  2. My main() function looks bad, is there any way to improve that?
  3. I'm not sure how to improve whitespace usage in the code. Is there a better way to split the logical sections of the code / a standard model to follow (apart from PEP-8 which I try to follow)?
Graipher
41.7k7 gold badges70 silver badges134 bronze badges
asked Jun 4, 2017 at 12:14
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You could make your own import statement by using the importlib, where the object to be imported (and also the module name) can be supplied as strings:

import importlib
def import_from(package, what):
 return getattr(importlib.import_module(package), what)
def get_algorithm(arg_algo):
 try:
 return import_from("hashlib", arg_algo)
 except AttributeError:
 print(f"Sorry, the algorithm {arg_algo} seems not to be installed on your system.")
 print("Choose from one of the available algorithms:")
 print(hashlib.algorithms_available)
 raise

This is simpler, because it uses the fact that Python namespaces are already similar to dictionaries, so there is no need to build a dictionary of possible hashlibs, when you get the same information by trying to import them.

You can make it even simpler by directly calling getattr on hashlib, as noted by @Peilonrayz in the comments:

import hashlib
def get_algorithm(arg_algo):
 try:
 return getattr(hashlib, arg_algo)
 except AttributeError:
 print(f"Sorry, the algorithm {arg_algo} seems not to be installed on your system.")
 print("Choose from one of the available algorithms:")
 print(hashlib.algorithms_available)
 raise
answered Jun 6, 2017 at 13:20
\$\endgroup\$
7
  • \$\begingroup\$ Thanks for your time. Since you recommended using try: ... / except: error message ..., would that mean I should do that for the entire project, to catch as many bugs as possible? The general audience for such an application wouldn't be 'average' people, after all- \$\endgroup\$ Commented Jun 6, 2017 at 15:12
  • 1
    \$\begingroup\$ @Coal_ Well, even non-average people like to see nice error messages. And the message I wrote above is way more informative than AttributeError: module 'hashlib' has no attribute 'md4', because it gives a way to distinguish between a typo (like here) and a hashlib not being installed. You can always write the error message more matter-of-factly: f"Algorithm {arg_algo} is not installed." \$\endgroup\$ Commented Jun 6, 2017 at 15:16
  • \$\begingroup\$ @Coal_ But in genereal I would only except errors, where you do something about them. Either because you know what to do in that case (like when you divide by zero, you maybe set the result to zero instead) or if you can supply additional information needed to fix the bug (as in this case, where the available algorithms are printed). \$\endgroup\$ Commented Jun 6, 2017 at 15:18
  • 1
    \$\begingroup\$ Thanks. I'll have a look at the code to see where custom error messages are best implemented. \$\endgroup\$ Commented Jun 6, 2017 at 15:32
  • 1
    \$\begingroup\$ Rather than using importlib, you can just import hashlib and getattr on hashlib. \$\endgroup\$ Commented Jun 7, 2017 at 1:08

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.