Anth's Computer Cave Tutorials

Python Password Analyzer

In this series we'll create a new open-source password-cracking program for penetration testing, and for clowns who've locked themselves out of their files.

First we'll build scripts to analyze passwords and find the average number of guesses for different password lengths and guessing methods, then we'll use the results to design our program.

This is an ongoing series, and we'll add to it every week. Use the links below to read each article.


Password Analyzer one: Random

I decided to take a day off from my main Python projects and I thought I might have a go at making a password analyzer to learn about password strength.

I knocked up a quick script to see how long it would take to break passwords randomly.

Basic single-password code

Within no time I was getting some interesting results. This Code takes a two-character password and guesses randomly until it finds the right characters. You can try it yourself by copying and pasting the code into an empty IDLE window. Save and run the code.

You'll be prompted to enter a password to test.

# Randomly crack a two-character password.
import random
pw = input("Enter the password you wish to test:\n")
# Characters to create random passwords
characters = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0",\
 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",\
 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
# Flag to stop guessing when attempt is sucessfull
status = "ongoing"
password_length = 2

def crack():
    global pw, status, password_length
    # Number of guesses
    count = 0    
    while status == "ongoing":
        guess = ""
        chars = 0
        # Create a string from randomly chosen characters
        while chars < password_length: >
            # Add a random index from the characters list
            guess += random.choice(characters)
            chars += 1
        # Compare guess to the real password
        if guess == pw:
            print("Password cracked!: " + str(pw))
            status = "finished"
            count += 1
            print(str(count) + " guesses")
        else:
            count += 1
crack()

There is a list holding strings for all lower case letters, as well as numbers from 0 to 9. The password_length variable specifies the number of characters in your password.

The crack() function creates a random combination from the characters list and compares it to the target password. It repeats this until it guesses the correct password.

The script then prints the password and the number of guesses to crack it.

Note that this method is using a completely random guess for each attempt. This means the number of guesses will vary widely when pitted against the same password again. I ran this four times and got results from 125 to 3550. Thus it really only provides us with an interesting insight into the law of averages, and there is nothing to stop Python trying the same combinations many times throughout the run.

Bulk-password code

Still, I was having fun, so I decided to run the script on many passwords to get averages from a larger sample-size. I created a guesses[] list to hold the number of attemps for each password cracked, then wrote a while loop that runs the crack() function 100 times, generating a new random target password before each run.

# Randomly crack a two-character password one hundred times and
#  display avarage number of guesses for each.
import random

# Target password to crack
pw = ""
# Characters to create random passwords
characters = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0",\
 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",\
 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
# Flag to stop guessing when attempt is sucessfull
status = "ongoing"
password_length = 2
# Store the number of guesses for each sucessfull password crack
guesses = []
# Sample size: the number of password to crack before calculating results
quota = 0

# Generat random passwords and compare to target password until match found.
def crack():
    global pw, status, password_length, guesses
    # Number of guesses
    count = 0    
    while status == "ongoing":
        guess = ""
        chars = 0
        # Create a string from randomly chosen characters
        while chars < password_length: >
            # Add a random index from the characters list
            guess += random.choice(characters)
            chars += 1
        # Compare guess to the real password
        if guess == pw:
            print("Password cracked!: " + str(pw))
            status = "finished"
            count += 1
            print(str(count) + " guesses")
            guesses.append(count)
        else:
            count += 1

# Generat random target passwords and crack each until quota is reached
while quota < 100: >
    quota += 1
    status = "ongoing"
    pw = ""
    chars = 0
    # Create a string from randomly chosen characters
    while chars < password_length:  >
        # Add a random index from the characters list
        pw += random.choice(characters)
        chars += 1
    crack()

# Calculat average number of guesses for each password
total = 0
for i in guesses:
    total = total + i
avg = total / len(guesses)
print(str(quota) + " passwords cracked.")
print("Average: " + str(avg) + " guesses per password.")

Lastly, I wrote a for loop to add the total amount of guesses, then divide that figure by 100.

In just seconds the program generates then guesses 100 two-character passwords. It then presents you with the average number of guesses required for each password

The first thing I noticed was the 1218 guesses per password was suspiciously close the the number of available characters squared (36 * 36 = 1296). We'll probably see more of that later.

The logical thing to do next was start on longer passwords. This is easy with the existing code, you just need to change the password_length variable on line 12.

password_length = 3

Guessing three-character passwords definately took longer, but it was still quicker than I expected.

Notice it took nearly 40 times more guesses per password than with two characters in each. This time the 47,023 average was really close to 36 * 36 * 36 (46,656).

Next I tried it with four-character passwords, and this takes much longer. To avoid wasting an hour of your time, change the while statement on line 40 to 10 rather than 100.

while quota < 10:

This is the amount of passwords to sucessfully crack before stopping.

The four-character passwords averaged one-and-a-half million guesses per password!

The 1,564,639 average is this time close to 36 * 36 * 36 *36 (1,679,616). Can you see a pattern here? Each extra character you add to the password length multiplies the number of guesses by around 36, the number of available characters in our list of letters and numbers

So far we have a script that can ramdomly guess four-character passwords quickly. Add another character and it takes many more guesses. As I write this I am watching Python crack five-character passwords. I have changed the script to crack just one pass word. The first run took 12 million guesses. The second run took 110 million guesses.

There are several things wrong with this approach. Randomly guessing the password each attempt may not be the best method

Firstly how many of those guesses are duplicates?. We could be wasting many attempts comparing the same passwords repeatedly. We'll add a method to determine this next week, but I can already see some worrying signs.

If the average number of guesses for a four-character password is 36 * 36 * 36 * 36, that means on average it is taking the maximum amount of guesses that should be required to try every combination for that password length. In theory the average should be much less, unless somehow the password was always the last available combination available to try.

Secondly, it's not predictable. While in theory it could succeed on the first guess, it could also try many times more than the necessary number of combinations for that password length. There is no way to predict the maximum number of guesses required

This is obviously not the best way to crack passwords and our finished program will not use this method.

It is, however, an insight into how much difference even a slight increase in password length makes. Remember, too, we are only using numbers between 0 - 9 and lower-case letters a - z. Adding upper-case letters and the remaining keyboard symbols would compound things again.

Next week we'll use a more methodical brute-force approach that gives us an absolute-maximum number of guesses the password could take to guess in advance, and creates no duplicate guesses. Then we'll add a dictionary-based option.

Until next week....

Cheers

Anth


Previous:

Next: Brute-force password analyzer

_____________________________________________


Comments

Leave a comment on this article