Python Projects

Explaining a Python Password Hash Cracker

I chose this project because I wanted to improve my technical knowledge of how hashes and hash cracking works, and build my understanding of what’s happening under the hood in software included in Kali, like Hashcat and Jack the Ripper. I also wanted to expand my skills in creating simple cyber engineering tools with python.

My scope for this project was:

Write a custom password cracker that takes the rockyou password file from the seclists github repository, hashes each password to sha256, and compares it to a CSV of usernames and passwords. Once all passwords are found in the list, print them out with their corresponding username and end the program.

The password hash table I created for this project. I hashed the plaintext passwords here: https://10015.io/tools/sha256-encrypt-decrypt

Now, lets work through the code!

import hashlib
import csv
from urllib.request import urlopen

The first 3 lines import the following python modules:

  • hashlib for hashing to sha256
  • csv to read the captured password file
  • urlopen from urllib.request to read the rockyou wordlist file. method
def read_wordlist(url):
    """ Takes a URL and returns the file content """
    try:
        wordlist_file = urlopen(url).read()
    except Exception as e:
        print("There was an error while reading the wordlist, error:", e)
        exit()
    return wordlist_file

This function takes the URL as a parameter and returns the file content. Further down we pass the URL to the url variable. If there is any exception it prints a message and the error, then exits the script.

def hash_password(password):
    """ 
    Takes the password and returns the SHA256 hash of the password as a double-
    length string, containing only hexadecimal digits.
    """
    result = hashlib.sha256(password.encode())
    return result.hexdigest()

The hash_password function takes the password and returns a SHA256 hash of the password. Expand.

url = 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Leaked-Databases/rockyou-75.txt'

Next we pass the URL of the rockyou-75.txt wordlist to the url variable.

Some other options we could have used here are to open the text file directly with 'open(file_path, 'r')‘ (for example if you were running this script from Kali) or we could insert a user input with ‘wordlist = input(“Enter wordlist filepath/URL: )’ so the user could choose a different wordlist each time they ran the script.

CSV_FILE_PATH = 'hashed_passwords.csv'

Here we define the CSV file path constant variable which contains the usernames and hashed passwords. Similar to the above we could change this to user input which would allow us to easily run the script with different hashed password lists (as long as they conform to the structure of a username column followed by a hashed password column.

user_password_dict = {}

Here we create a dictionary to store username-password pairs, which we use to print out the cracked passwords and their corresponding username.

with open(CSV_FILE_PATH, mode='r') as file:
    csv_reader = csv.DictReader(file)
    for row in csv_reader:
        username = row['username']
        hashed_password = row['hashed_password']
        user_password_dict[hashed_password] = username

In this block we open the csv file and and set the mode to read. On the next line we create a CSV reader object for reading the rows from the CSV file as dictionaries. This is specifically designed for reading CSV files where the first row contains headers and the following rows contain data.

In the third line, we start a for loop that iterates over each row in the CSV file. In the following 2 lines we assign the value of the column header to a corresponding variable. The final line adds a new key-value pair to the ‘user_password_dict’ dictionary,

wordlist = read_wordlist(url).decode('UTF-8')

In this line we read the rockyou wordlist from the URL and convert to SHA256 hashes.

rockyou_passwords = [password.strip() for password in wordlist.split('\n')]

Here we create a list of passwords from the wordlist. It begins by splitting the wordlist into a list of strings at each new line character, then uses the ‘strip()’ method to remove leading and trailing whitespace. It then assigns the list to the variable ‘rockyou_passwords’.

rockyou_hashes = [hash_password(password) for password in rockyou_passwords]

This line of code is a list comprehension that iterates over each password in the ‘rockyou_passwords’ list (defined above) and calls the ‘hash_password’ function to hash that password and finally adds it to the ‘rockyou_hashes’ list.

matches = set(user_password_dict.keys()) & set(rockyou_hashes)

This line is performing a set intersection operation. It takes all of the dictionary keys from ‘user_password_dict’ (our hashed user passwords) and the hashed wordlist passwords from ‘rockyou_hashes’ and converts them both to sets. The ‘&’ operator goes through both sets and creates a new set called ‘matches’ out of the elements that are in both original sets.

The result is that we have a set of matching password hashes, and because we know the plaintext of the rockyou wordlist passwords, we can map that to usernames and passwords.

if matches:
    print("Password matches found...")
    for match in matches:
        print("Username:", user_password_dict[match], " \tPassword: ", rockyou_passwords[rockyou_hashes.index(match)])
else:
    print("No password matches found.")

In our final block of code we have an if loop that iterates through the matches set and prints a message if any password matches are found. It then iterates through the list of matches and prints the username and corresponding plaintext password.

Running the script we can see that it has cracked 14 of the 16 crappy passwords in my test CSV!

This project shows how easily common passwords can be cracked with some simple python code (and would be almost instant with a dedicated cracking application such as Burp Suite or John the Ripper). It reinforces the importance of having strong passwords and when storing passwords its much more secure to use a hashing function that salts the original, which makes it harder to match hashes like we have done.

The full code for this project can be found on my Github.