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)?
\$\endgroup\$
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
\$\endgroup\$
  • \$\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\$ – Daniel Jun 6 '17 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\$ – Graipher Jun 6 '17 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\$ – Graipher Jun 6 '17 at 15:18
  • 1
    \$\begingroup\$ Thanks. I'll have a look at the code to see where custom error messages are best implemented. \$\endgroup\$ – Daniel Jun 6 '17 at 15:32
  • 1
    \$\begingroup\$ Rather than using importlib, you can just import hashlib and getattr on hashlib. \$\endgroup\$ – Peilonrayz Jun 7 '17 at 1:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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