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.
- 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 toalgorithms
? I tried using list comprehensions andhashlib.algorithms_available
, but couldn't figure it out. - My
main()
function looks bad, is there any way to improve that? - 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)?
1 Answer 1
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
-
\$\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– Daniel2017年06月06日 15:12:37 +00:00Commented 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\$Graipher– Graipher2017年06月06日 15:16:24 +00:00Commented 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\$Graipher– Graipher2017年06月06日 15:18:17 +00:00Commented 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\$Daniel– Daniel2017年06月06日 15:32:32 +00:00Commented Jun 6, 2017 at 15:32
-
1\$\begingroup\$ Rather than using
importlib
, you can justimport hashlib
andgetattr
onhashlib
. \$\endgroup\$2017年06月07日 01:08:25 +00:00Commented Jun 7, 2017 at 1:08