< BACK TO PYTHON PROJECTS

Building a Hash Cracker using Python (Source Code)

Faraz

By Faraz - October 19, 2023

Unlock the secrets of password hashing in Python. Create a secure hash cracker and protect your data from cyber threats.


Building a Hash Cracker using Python.jpg

In the world of cybersecurity, protecting sensitive information is of utmost importance. One common method of securing data is through hashing. However, there are times when you may need to reverse the process, known as hash cracking. In this article, we'll delve into the intriguing realm of hash cracking and explore how to build a Hash Cracker using Python.


Table of Contents


  1. Introduction to Hash Cracking
  2. What is a Hash?
  3. How Hash Cracking Works
  4. Benefits of Building a Hash Cracker
  5. Understanding Python for Hash Cracking
  6. Setting Up Your Environment
  7. Choosing the Right Hash Cracking Library
  8. Full Source Code
  9. Explanation of Source Code
  10. Conclusion
  11. FAQs

1. Introduction to Hash Cracking


Hash cracking is the process of attempting to reverse a hashed value back to its original, unhashed form. This is particularly useful when you have forgotten a password or need to test the security of a system. Python, a versatile and powerful programming language, provides the perfect platform for building your own hash cracker.


2. What is a Hash?


Before we dive deeper, let's understand what a hash is. A hash is a cryptographic function that takes an input (or 'message') and returns a fixed-length string of characters, which is typically a hexadecimal number. The key feature of a hash is that it's a one-way function, meaning you can't easily reverse it to retrieve the original input.


3. How Hash Cracking Works


Hash cracking involves trying various inputs until you find a match with the target hash. There are two main methods: dictionary attacks and brute-force attacks.


  • Dictionary Attacks: In this method, a list of potential passwords, known as a dictionary, is used to compare against the hash. If there's a match, the original password is found.
  • Brute-Force Attacks: Brute-force attacks try every possible combination of characters until the correct one is found. While effective, they can be time-consuming.

4. Benefits of Building a Hash Cracker


Building your own hash cracker using Python offers several advantages:


  • Customization: You can tailor it to your specific needs and objectives.
  • Learning: It's an excellent way to deepen your understanding of both Python and cybersecurity.
  • Security Testing: You can test the robustness of your own security systems or those of others ethically.

5. Understanding Python for Hash Cracking


Python is a popular choice for hash cracking due to its simplicity and vast library support. Its readability and ease of use make it accessible even for those new to programming.


6. Setting Up Your Environment


First, ensure you have Python installed on your system. Additionally, you'll need a text editor for writing your code. IDEs like PyCharm or Jupyter Notebook are great choices.


7. Choosing the Right Hash Cracking Library


Python offers various libraries for hash cracking. Some popular ones include 'hashlib' and 'bcrypt.' Select the one that best suits your needs and project requirements.


8. Full Source Code


#!/usr/bin/env python3

import hashlib
import itertools
import multiprocessing
import os
import string
import threading
import time


class Cracker(object):
    ALPHA_LOWER = (string.ascii_lowercase,)
    ALPHA_UPPER = (string.ascii_uppercase,)
    ALPHA_MIXED = (string.ascii_lowercase, string.ascii_uppercase)
    PUNCTUATION = (string.punctuation,)
    NUMERIC = (''.join(map(str, range(0, 10))),)
    ALPHA_LOWER_NUMERIC = (string.ascii_lowercase,
                           ''.join(map(str, range(0, 10))))
    ALPHA_UPPER_NUMERIC = (string.ascii_uppercase,
                           ''.join(map(str, range(0, 10))))
    ALPHA_MIXED_NUMERIC = (
        string.ascii_lowercase, string.ascii_uppercase, ''.join(map(str, range(0, 10))))
    ALPHA_LOWER_PUNCTUATION = (string.ascii_lowercase, string.punctuation)
    ALPHA_UPPER_PUNCTUATION = (string.ascii_uppercase, string.punctuation)
    ALPHA_MIXED_PUNCTUATION = (
        string.ascii_lowercase, string.ascii_uppercase, string.punctuation)
    NUMERIC_PUNCTUATION = (''.join(map(str, range(0, 10))), string.punctuation)
    ALPHA_LOWER_NUMERIC_PUNCTUATION = (string.ascii_lowercase, ''.join(
        map(str, range(0, 10))), string.punctuation)
    ALPHA_UPPER_NUMERIC_PUNCTUATION = (string.ascii_uppercase, ''.join(
        map(str, range(0, 10))), string.punctuation)
    ALPHA_MIXED_NUMERIC_PUNCTUATION = (
        string.ascii_lowercase, string.ascii_uppercase, ''.join(
            map(str, range(0, 10))), string.punctuation
    )

    def __init__(self, hash_type, hash, charset, progress_interval):
        """
        Sets the hash type and actual hash to be used
        :param hash_type: What algorithm we want to use
        :param hash: The hash in base64 format
        :return:
        """
        self.__charset = charset
        self.__curr_iter = 0
        self.__prev_iter = 0
        self.__curr_val = ""
        self.__progress_interval = progress_interval
        self.__hash_type = hash_type
        self.__hash = hash
        self.__hashers = {}

    def __init_hasher(self):
        hashlib_type = self.__hash_type if self.__hash_type != "ntlm" else "md4"
        self.__hashers[self.__hash_type] = hashlib.new(hashlib_type)

    def __encode_utf8(self, data):
        return data.encode("utf-8")

    def __encode_utf16le(self, data):
        return data.encode("utf-16le")

    @staticmethod
    def __search_space(charset, maxlength):
        """
        Generates the search space for us to attack using a generator
        We could never pregenerate this as it would take too much time and require godly amounts of memory
        For example, generating a search space with a rough size of 52^8 would take over 50TB of RAM
        :param charset: The character set to generate a search space for
        :param maxlength: Maximum length the search space should be capped at
        :return:
        """
        return (
            ''.join(candidate) for candidate in
            itertools.chain.from_iterable(
                itertools.product(charset, repeat=i) for i in
                range(1, maxlength + 1)
            )
        )

    def __attack(self, q, max_length):
        """
        Tries all possible combinations in the search space to try and find a match.
        This is an extremely tight loop so we need to inline and reduce work as much as we can in here.
        :param q: Work queue
        :param max_length: Maximum length of the character set to attack
        :return:
        """
        self.__init_hasher()
        self.start_reporting_progress()
        hash_fn = self.__encode_utf8 if self.__hash_type != "ntlm" else self.__encode_utf16le
        for value in self.__search_space(self.__charset, max_length):
            hasher = self.__hashers[self.__hash_type].copy()
            self.__curr_iter += 1
            self.__curr_val = value
            hasher.update(hash_fn(value))
            if self.__hash == hasher.hexdigest():
                q.put("FOUND")
                q.put("{}Match found! Password is {}{}".format(
                    os.linesep, value, os.linesep))
                self.stop_reporting_progress()
                return

        q.put("NOT FOUND")
        self.stop_reporting_progress()

    @staticmethod
    def work(work_q, done_q, max_length):
        """
        Take the data given to us from some process and kick off the work
        :param work_q: This is what will give us work from some other process
        :param done_q: Used to signal the parent from some other process when we are done
        :param max_length: Maximum length of the character set
        :return:
        """
        obj = work_q.get()
        obj.__attack(done_q, max_length)

    def start_reporting_progress(self):
        self.__progress_timer = threading.Timer(
            self.__progress_interval, self.start_reporting_progress)
        self.__progress_timer.start()
        print(
            f"Character set: {self.__charset}, iteration: {self.__curr_iter}, trying: {self.__curr_val}, hashes/sec: {self.__curr_iter - self.__prev_iter}",
            flush=True)
        self.__prev_iter = self.__curr_iter

    def stop_reporting_progress(self):
        self.__progress_timer.cancel()
        print(
            f"Finished character set {self.__charset} after {self.__curr_iter} iterations", flush=True)


if __name__ == "__main__":
    character_sets = {
        "01": Cracker.ALPHA_LOWER,
        "02": Cracker.ALPHA_UPPER,
        "03": Cracker.ALPHA_MIXED,
        "04": Cracker.NUMERIC,
        "05": Cracker.ALPHA_LOWER_NUMERIC,
        "06": Cracker.ALPHA_UPPER_NUMERIC,
        "07": Cracker.ALPHA_MIXED_NUMERIC,
        "08": Cracker.PUNCTUATION,
        "09": Cracker.ALPHA_LOWER_PUNCTUATION,
        "10": Cracker.ALPHA_UPPER_PUNCTUATION,
        "11": Cracker.ALPHA_MIXED_PUNCTUATION,
        "12": Cracker.NUMERIC_PUNCTUATION,
        "13": Cracker.ALPHA_LOWER_NUMERIC_PUNCTUATION,
        "14": Cracker.ALPHA_UPPER_NUMERIC_PUNCTUATION,
        "15": Cracker.ALPHA_MIXED_NUMERIC_PUNCTUATION
    }

    hashes = {
        "01": "MD5",
        "02": "MD4",
        "03": "LM",
        "04": "NTLM",
        "05": "SHA1",
        "06": "SHA224",
        "07": "SHA256",
        "08": "SHA384",
        "09": "SHA512"
    }

    prompt = "Specify the character set to use:{}{}".format(
        os.linesep, os.linesep)
    for key, value in sorted(character_sets.items()):
        prompt += "{}. {}{}".format(key, ''.join(value), os.linesep)

    while True:
        try:
            charset = input(prompt).zfill(2)
            selected_charset = character_sets[charset]
        except KeyError:
            print("{}Please select a valid character set{}".format(
                os.linesep, os.linesep))
            continue
        else:
            break

    prompt = "{}Specify the maximum possible length of the password: ".format(
        os.linesep)

    while True:
        try:
            password_length = int(input(prompt))
        except ValueError:
            print("{}Password length must be an integer".format(os.linesep))
            continue
        else:
            break

    prompt = "{}Specify the hash's type:{}".format(os.linesep, os.linesep)
    for key, value in sorted(hashes.items()):
        prompt += "{}. {}{}".format(key, value, os.linesep)

    while True:
        try:
            hash_type = hashes[input(prompt).zfill(2)]
        except KeyError:
            print("{}Please select a supported hash type".format(os.linesep))
            continue
        else:
            break

    prompt = "{}Specify the hash to be attacked: ".format(os.linesep)

    while True:
        try:
            user_hash = input(prompt)
        except ValueError:
            print("{}Something is wrong with the format of the hash. Please enter a valid hash".format(
                os.linesep))
            continue
        else:
            break

    print(f"Trying to crack hash {user_hash}", flush=True)
    processes = []
    work_queue = multiprocessing.Queue()
    done_queue = multiprocessing.Queue()
    progress_interval = 3
    cracker = Cracker(hash_type.lower(), user_hash.lower(),
                      ''.join(selected_charset), progress_interval)
    start_time = time.time()
    p = multiprocessing.Process(target=Cracker.work,
                                args=(work_queue, done_queue, password_length))
    processes.append(p)
    work_queue.put(cracker)
    p.start()

    if len(selected_charset) > 1:
        for i in range(len(selected_charset)):
            progress_interval += .2
            cracker = Cracker(hash_type.lower(), user_hash.lower(),
                              selected_charset[i], progress_interval)
            p = multiprocessing.Process(target=Cracker.work,
                                        args=(work_queue, done_queue, password_length))
            processes.append(p)
            work_queue.put(cracker)
            p.start()

    failures = 0
    while True:
        data = done_queue.get()
        if data == "NOT FOUND":
            failures += 1
        elif data == "FOUND":
            print(done_queue.get())
            for p in processes:
                p.terminate()

            break

        if failures == len(processes):
            print("{}No matches found{}".format(os.linesep, os.linesep))
            break

    print("Took {} seconds".format(time.time() - start_time))

9. Explanation of Source Code


Here's an explanation of the key components and how it works:


1. The Cracker class defines the core functionality of the password cracking tool. It has various class attributes representing character sets (e.g., lowercase letters, uppercase letters, digits, etc.), and it allows the user to select a character set, hash type, and target hash.


2. The __init__ method initializes the Cracker object with the selected hash type, target hash, character set, and progress interval.


3. The __init_hasher method initializes the hash function based on the selected hash type.


4. The __encode_utf8 and __encode_utf16le methods encode data in UTF-8 and UTF-16LE formats, respectively.


5. The __search_space method generates a search space of possible combinations of characters from the given character set and maximum length. This is done using itertools.product to generate all possible permutations of characters.


6. The __attack method is responsible for iterating through the search space and hashing each combination of characters. If a match is found, it notifies the main thread through a queue.


7. The start_reporting_progress method starts a timer to periodically display the progress of the password cracking attempt. It shows the current character set, iteration count, the value being tried, and the hash rate.


8. The stop_reporting_progress method stops the progress reporting timer and prints a message indicating the completion of the character set.


9. The work method is a static method used as a worker function in a multiprocessing context. It takes a Cracker object, a work queue, and a done queue. It initiates the attack by calling the __attack method.


10. In the main part of the script, it defines a dictionary of character sets and hashes for the user to choose from. It prompts the user to select a character set, specify the maximum password length, select a hash type, and input the target hash.


11. It starts a multiprocessing environment to distribute the password cracking work across multiple processes.


12. The Cracker object is created with the user's selected parameters, and a process is initiated for this Cracker object.


13. If the selected character set contains more than one set of characters, additional processes are created for each character set variation.


14. The script monitors the done queue for results. If a match is found, it prints the cracked password and terminates all processes. If no matches are found after all processes have finished, it informs the user.


15. The script measures and prints the time taken for the cracking attempt.


This tool allows for distributed password cracking using multiple character sets and processes, making it more efficient and versatile. It is crucial to use such tools responsibly and legally, as password cracking without proper authorization is illegal and unethical.


10. Conclusion


Building a Hash Cracker using Python is a fascinating endeavor that can both enhance your programming skills and deepen your understanding of cybersecurity. With this tool, you'll be equipped to test and strengthen the security of systems and databases. Remember, with great power comes great responsibility.


11. FAQs


Q1. Is it legal to build a hash cracker using Python?

Building a hash cracker is legal, but using it without proper authorization is not. Always adhere to legal and ethical guidelines.


Q2. What is the difference between a dictionary attack and a brute-force attack?

A dictionary attack uses a predefined list of passwords, while a brute-force attack tries every possible combination of characters.


Q3. Can I use my hash cracker to test the security of my own systems?

Absolutely. Building your hash cracker is an excellent way to enhance the security of your systems ethically.


Q4. Which hash cracking library is best for beginners?

The 'hashlib' library is a good choice for beginners due to its simplicity and ease of use.


Q5. What are the consequences of using a hash cracker without authorization?

Using a hash cracker without proper authorization can lead to legal consequences and significant penalties. Always use it responsibly and ethically.

That’s a wrap!

I hope you enjoyed this article

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post