Brute-forcing MD5 Hashes with Python

by Manuel Morfin

Brute-forcing MD5 Hashes with Python

Share


Have you ever wondered how brute-forcing works? In this project, we will explore one way to brute-forcing MD5 hashes using a distributed approach with Python. We will create a server that sends brute-force tasks to multiple client bots that will perform the cracking and return the result to the server.

How Password Brute-forcing Works

Password brute-forcing is the process of guessing or cracking a password by trying different combinations of characters until the correct one is found. This method is very time-consuming and can take days, weeks, or even months to crack a password, depending on the length and complexity of the password.

Distributed Password Brute-forcing

Distributed password brute-forcing is a method of password cracking that uses multiple computers or nodes to perform the brute-force task simultaneously. This approach can significantly reduce the time required to crack a password, as each node can work on a different set of combinations simultaneously.

Disclaimer

🛑
This project was created as a way for me to learn about distributed systems. The goal was to create a distributed system that would allow me to brute-force 'passwords' that have been hashed. These passwords have been hashed using MD5 for the sake of simplicity. At no point in time should you ever store passwords as MD5 hashes, let alone without some form of salting. That being said, the code presented here can always be modified to bruteforce different hashing algorithims.

Setting up the Server

In this project, we will create a Python server that will send brute force password cracking tasks to multiple client bots. The server will listen on a specific IP address and port for incoming connections from the client bots. When a client bot connects, the server will add it to a list of connected clients and wait for incoming messages from the client bots.

Implementing the Server

# imports the libraries needed
import socket, string
from threading import Thread

# defines the hostname and reserves the port that will be utilized to connect with this server
SERVER_HOST = "192.168.50.5"
SERVER_PORT = 32401 

# Defines a blank global list to store connections
client_sockets = []

## creates a new socket, AF_INET for IPV4 and SOCK_STREAM for TCP 
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((SERVER_HOST, SERVER_PORT))

# listens for incoming connections
s.listen(5)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

# list of characters that will be used to generate permutations
chars = string.ascii_lowercase + string.ascii_uppercase + string.digits + '!@#$%'

# function used to send commands to the bots
def sendCommand():
  # sends the shell command to all the clients on the clientList
        print("Enter an MD5 to crack$", end=" ")
        # sends the MD5 hash received
        command = input()
        # sends it to the clients
        print("Please wait while we crack the password")
        client_sockets[0].send(chars.encode())
        client_sockets[0].send(command.encode())
        client_sockets[1].send(chars[::-1].encode())
        client_sockets[1].send(command.encode())  

# function used to listen for incoming connections from the bots
def listen_for_client(cs):
    while True:
        try:
            # keep listening for a message 
            if(len(client_sockets) == 2):
                sendCommand()
            # receives a message from the client
            msg = cs.recv(1024).decode()
            
            # checks to makes sure the client is not empty 
            if(msg):
                print(msg)
                raise Exception('We found the hash!')
                client_sockets.remove(cs) 	          
          
        except Exception as e:
            # client no longer connected
            # remove it from the list
            print(f"[!] Ending the process: {e}")
            client_sockets.remove(cs)
            break
           
while True:
    #Attends to new connections
    client_socket, client_address = s.accept()
    print(f"[+] {client_address} connected.")
    # Add new connections to a list
    client_sockets.append(client_socket)
    t = Thread(target=listen_for_client, args=(client_socket,))
    t.daemon = True
    t.start()

# close client sockets
for cs in client_sockets:
    cs.close()
# close server socket
s.close()

Implementing the Client Bot

Once the server is set up, we can create the client bot that will perform the brute force password cracking. The client bot will connect to the server, receive the task from the server, perform the cracking, and send the result back to the server.

# imports the libraries needed
import socket, hashlib, string
from itertools import product

# defines the hostname and port to connect to the server
SERVER_HOST = "192.168.50.5"
SERVER_PORT = 32401 

# creates a new socket, AF_INET for IPV4 and SOCK_STREAM for TCP
client = socket.socket()

# connects to the server
client.connect((SERVER_HOST, SERVER_PORT))

while True:
    # receives the character set to use for the brute force from the server
    chars = client.recv(1024).decode()
    
    # receives the MD5 hash to crack from the server
    md5hash = client.recv(1024).decode()
    
    # creates a product of all the characters of the specified length
    for length in range(1, 5):
        for attempt in product(chars, repeat=length):
            # converts the attempt to a string
            attempt = ''.join(attempt)
            
            # calculates the MD5 hash of the attempt
            hashed_attempt = hashlib.md5(attempt.encode()).hexdigest()
            
            # checks if the hashed attempt matches the provided MD5 hash
            if hashed_attempt == md5hash:
                # sends the result to the server
                client.send(f"[*] Password found: {attempt}".encode())
                break
    
    # closes the connection
    client.close()

Running the Project

To run the project, we need to start the server and the client bots. First, we need to start the server by running the server.py file:‌

python server.py

Once the server is running, we can start the client bots by running the client.py file on multiple machines:‌

python client.py

After the connection is established, the server sends a shell command to the client to start the password cracking process. The client receives the command, executes the brute force function, and sends the result back to the server.

Once the result is sent, the client connection is closed. The server receives the result and terminates the connection with the client.

Server:

[*] Listening as 192.168.50.5:32401
[+] ('192.168.50.6', 54321) connected.
[+] ('192.168.50.7', 54322) connected.
Enter an MD5 to crack$ d077f244def8a70e5ea758bd8352fcd8
Please wait while we crack the password

The password is: 1234

[!] Ending the process: We found the hash!
[!] Ending the process: We found the hash!

Bot 1:

List Received: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$% 

The password is: 1234
Total runtime of the program is 5.000279903411865

Bot 2:

List Received: %$#@!9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba

The password is: 1234
Total runtime of the program is 1.2307019233703613

Conclusion

In this project, we explored one way of cracking passwords using a distributed approach with Python. We created a server that sends brute force password cracking tasks to multiple client bots that will perform the cracking and return the result to the server. This approach can significantly reduce the time required to crack a password and highlights the importance of using strong passwords to protect sensitive information.