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 three: Brute-force 2

Today we'll look at the finished version of our brute-force password analyzer we started in the last article.

Click the button below to copy the full code.


There are some new options in todays code and some of the existing options have changed.

The password length is now auto-ranging. You can choose a starting length and when all combinations have been tried it will automatically increase the length and start again.

Their are new character array options that include upper and lower case, just lower case, and a numeric array.

You can also now easily switch between manually entering a single target password and running bulk tests against multiple random passwords.

New characters array

Adding the upper-case letters to the characters array has really slowed the guessing process. The number of guesses required has increased roughly ten-fold.

For this reason I've kept the option of using just lower-case letters. After all, not everybody uses upper-case letters.

You select which array to use by changing the mode variable

if mode == "string_full":
    characters = ["s", "a", "t", "c", "b", "d", "e", "f", "w", "g", "h", "i", \
                  "l", "p", "r", "m", "u", "n", "o", "j", "k", "x", "v", "y", \
                  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "q", "z", \
                  "S", "A", "T", "C", "B", "D", "E", "F", "W", "G", "H", "I", \
                  "L", "P", "R", "M", "U", "N", "O", "J", "K", "X", "V", "Y", \
                  "Q", "Z"]
# Lower case letters with numbers
elif mode == "string_lower":
    characters = ["s", "a", "t", "c", "b", "d", "e", "f", "w", "g", "h", "i", \
                  "l", "p", "r", "m", "u", "n", "o", "j", "k", "x", "v", "y", \
                  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "q", "z"]
# Numeric characters only for PIN-type passwords    
    characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

There is also a numeric array for PIN-type passwords. This can find numeric passwords much faster than the equivalent-length alpha-numeric password. It may be a good first option before moving on to an alpha-numeric quest.

I've moved the letters around in the array to get some of the more common starting letters to the front. The left-hand characters of the password are the real decider of how many guesses are required

elif mode == "string_lower":
    characters = ["s", "a", "t", "c", "b", "d", "e", "f", "w", "g", "h", "i", \
                  "l", "p", "r", "m", "u", "n", "o", "j", "k", "x", "v", "y", \
                  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "q", "z"]

I've based this on a word list I've been creating for the next article. I've made a text-grabber program which stores words in alphabetical sections and prints the number of words starting with each letter. The results were from a sample-size of 8000 words.

I've left the numbers towards the end of the array on the assumption that people are more likely to put numbers at the end of their password, and we're trying to get the letters at the beginning.

I haven't decided where to position the upper-case letters yet, they're just tacked onto the end of the array in the same order as the lower-case letters. I have a feeling they may be better set alongside their lower-case equivalents. I'm assuming people would be more likely to use an upper-case letter at the start of their password out of habit from general writing norms.

Password length

The program now starts guessing at your selected length and increases the length automatically once all combinations have been used. The methods for selecting your starting length varies depending on whether you are manually entering a single password or running a bulk test on random passwords.

To manually test a password, go to line 40 and set the password_length variable to your desired starting length.

To test multiple random passwords, change the pass_length variable on line 46 to your desired starting point. This time, also change the password_length_to_start variable to the same figure. This is used to reset the password_length variable between each successful run. Finally, change the password_length_to_generate variable on line 50 to the length of the random passwords you would like to generate.

As an example, the configuration below would generate four-character target passwords, but the loop will start guessing three-character passwords and work upwards until it finds your four-character password.

# If guessing multiple passwords generate random passwords between runs
    target_password = ""
    # Current guessing length
    password_length = 3
    # Length to start guessing
    password_length_to_start = 3
    # Length of target passwords to create
    password_length_to_generate = 4

Running the program

We'll test a single password called 'test'. Change the reps[1] entry to 1.

# Number of passwords to crack
reps = [0, 1]

Save and run the program, and enter the test password when prompted.

Guessing a four-character password

I've set my password_length to begin guessing two-character passwords and you can see it has reach 'zz' then moved on to three-character guesses. When those combinations were exhausted it move on to four-characters and soon found my password, 'test'

To run a bulk test against multiple random passwords change the reps[1] entry to the number of passwords you want to break.

Guessing five four-character passwords.

This was the results from running five four-character password tests with guesses starting at three-characters.

Mode comparisons

Let's compare the different number of guesses required for the different modes.

Here is a test using upper and lower-case letters by setting the mode variable to 'string_full'.

Guessing four-character password.

That's half a million guesses for a four-character password. Now I'll change the mode variable to 'string_lower' and try again with the same password.

Guessing four-character password.

At 100,000 guesses, there is a hugh difference. Now for the numeric test.

Guessing four-character password.

With the mode variable set to 'numeric' it took just 3714 guesses for a four-character password. This is just three per cent of the lower-case mode and less than one per cent of the uppercase mode.

I chose a number that used the same indexes in the numeric characters list as 'test' does in the string characters list

Unless I definitely know there are letters in a target password, I think I would start with the numeric mode and let it zap through to four or six characters. After that I would use either the string_lower or string_full mode and get on with life for a while.

Once again, unless I was sure there were upper-case letters in the password I would take a gamble and use the faster string_lower method.

If the max guesses with lower-case letters is twenty-percent of the upper-case mode, the most you are loosing if lower-case mode fails is twenty-percent of the time it could take to run upper-case mode.

Once we start using this for real tasks you'll see the agonizing time that every guess takes, and you may need some luck to crack anything over four or five characters using brute-force methods alone.

Dictionary to the rescue

In the next article we start on our dictionary-based attack method. This is the only real hope of guessing long passwords in your lifetime. Using whole words, the password length is irrelevant. Each word is one guess, regardless of length.

The downside of course is the password you are guessing must be a whole word.

First we need to make a word list. We'll write a quick script to scrape text from files, and itemize them alphabetically in a JSON file. I've scraped 8000 unique words so far. While that's enough for initial testing, we'll need to add another hundred-odd thousand to use in the finished program.

Once we have our word list we can build the function to use the words. This should only require a few basic modifications to our existing code.



Previous: Brute-force password analyzer_1

Next: Create a word list



Leave a comment on this article