A secure password or passphrase is the main control protecting the vast majority of our online accounts from unauthorised access. A strong password is important because it acts like a sturdy lock protecting our information in the digital world. Even though many organisations are adopting passwordless authentication methods, passwords are still quite common so we need to ensure they cannot be easily guessed or brute-force cracked.
According to NIST (National Institute of Standards and Technology) the following guidelines should be followed to ensure a secure password: at least 8 characters (maximum of 64), every ASCII character should be used and consecutive or recurring characters should be avoided. I followed the NIST guidelines for the first and second versions of my Python Password Strength Checker.
NIST also recommends screening passwords against commonly used and breached password lists (which I have incorporated into version 3) and utilizing at least Two-Factor Authentication (2FA), which provides an additional layer of protection.
As I was preparing to write the third version of the program I came across the below table put together by Hive Systems showing the current time to brute force passwords in 2023 with modern GPUs. Based on this I revised the minimum character length from 8 to 12.
How Long to Crack a Password in 2023. Source: Hive Systems
Version 1 – Regular Expressions
In the first iteration of my password strength checker, I used regular expressions or regex. Regex are a sequence of characters that define a pattern.
I started by defining a regex pattern for both a strong and a weak password.
A breakdown of regex used in version 1:
?=.
.*
\d
[a-z]
[A-Z]
[`~!@#$%^&*?<>])
{8,140}
Positive look ahead
Matches any character and matches the previous token
Matches one digit
Matches a single lowercase
Matches a single uppercase
Matches any of these specials
The minimum and maximum length of the password
The first version did not include many of the available special characters, especially the ones having other uses in Python, for example, colons and hashes. Although I could escape the special characters with a backslash, I felt this would make the code even harder to read so I decided to take a different approach. I also found the regular expressions tedious to work with so I took the second version of the program in a different direction.
Version 2 – Variables and if loops
In the second iteration, I moved away from regex. I placed the characters in variables and used an if loop to increment the count for each variable we’re testing the password against. If one of the variables’ value hasn’t changed from 0 then it gives the result that the password is weak.
I was able to test against 27 special characters in this version compared to 19 in the first, however, there were still some specials that needed to be added (such as speech marks).
This one still did not include all of the special characters but was much easier to read and write the code. I knew there must be a more efficient way to include all of the special characters without having to escape each one, so I went looking for that in the final Version.
Final Version – importing strings and opening files
In the final program, I found a way to resolve the issue of not including all special characters and also added a separate function to check the password against a list of 10,000 of the most common. I repurposed a bit of code I was using for debugging to provide info to the user on where to improve their password strength. I have also updated the minimum length to 12 characters. I kept the bones of the previous code and will explain what I’ve done line by line below.
Above is the entire Python program and below we will go through it line by line.
#!/usr/bin/python3
The first line is used to specify the interpreter for running this script in UNIX-based systems.
Ensure the file permissions are set so the program can be executed with <sudo chmod u+x>. To execute the program from BASH enter <python3 password_strength_checker.py>
from string import punctuation
This line imports the built-in Python string module, which allows you to quickly access constants. From this, we import the punctuation constant into a variable.
lowercase="abcdefghijklmnopqrstuvwxyz"
uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
number="1234567890"
In the next three lines, I have kept the three variables from the previous version for lowercase and uppercase letters and numbers. I could have changed this to use strings constants ascii_lowercase, ascii_uppercase and digits, to maintain consistency with punctuation.ascii_lowercase.
def load_common_passwords():
""" Check user password against list of 10k common hacked passwords """
with open("10k-most-common.txt", "r") as file:
content = file.read()
return content
This block was originally part of the for loop below but I moved it into its own function.
As the comment states, it will check the password against a list of 10,000 of the most common passwords. The password list is from this GitHub. The first line uses the with keyword and open() built-in function. The with keyword calls two built-in methods behind the scenes which close the open file once we’re done with it and are used with the open function.
The “r” means reading the file.
def password_strength(pw):
Here I have defined a function called password_strength, which takes one argument, the user input of their password to be tested pw.
"""
Test password strength against variables and print if it passes or fails
"""
This line between three double quotation marks is a doc string, which is used after the definition of a function, module, class or method to describe the code.
l, u, n, p, c = 0, 0, 0, 0, 0
In this line, I have created a variable for each factor of the password to be tested (lowercase, uppercase, etc) and assigned a value of 0.
if (len(pw) >= 12):
This line starts an if statement checking the length of the argument pw and proceeding if it is 12 characters or over. This was increased from 8 in the previous two programs after double-checking my assumptions on the time to brute force passwords in 2023.
for i in pw:
if i in lowercase:
l+=1
if i in uppercase:
u+=1
if i in number:
n+=1
if i in punctuation:
p+=1
Next, we have a for loop, which iterates over the sequence of characters in the argument, representing the characters with the i variable. This is followed by if statements which check each character type separately and increment the variable by 1 for each.
content = load_common_passwords()
if (pw not in content):
c+=1
For the next part of this function, I have created a variable called content and passed the above function load_common_passwords() to it. The next line has an if statement that checks the password against the text file containing the list of passwords. If the password is not in the list, the c variable gets incremented by 1 like the previous variables.
strength = 0
if l >= 1:
strength += 1
if u >= 1:
strength += 1
if n >= 1:
strength += 1
if p >= 1:
strength += 1
if c >= 1:
strength += 1
This block is similar to the previous for loop but without the for loop. It starts with a new variable strength assigned a value of 0. The preceding if statements iterate through the previous variables assigned to each password factor and increment the strength variable by 1 for each hit.
if (strength >= 5):
print("\nYour password is strong and mighty!")
else:
print("\nYour password is weaksauce. Try again.")
This block has an if statement taking the value of the strength variable, which if it has the value of 5 or over, prints that the password is strong. Having 5 or over indicates that the password has all of the strength requirements.
The next part has an else statement which prints that the password is weak if the previous if statement is failed.
print(f"""\nMaking your password stronger:
- Length (must be 12 or higher) : {len(pw)}
- Lowercase (must contain at least 1) : {l}
- Uppercase (must contain at least 1) : {u}
- Number (must contain at least 1) : {n}
- Punctation (must contain at least 1) : {p}
- Must not be a common password : {c}
""")
I made this block for debugging and decided to modify it to provide the user with further information about their password and how to make it stronger.
It starts with a print() function, with an f so that the variable values can be called. There is a \n so that the print appears on a new line and a triple quotation mark so that the strings can be printed over multiple lines.
if __name__=="__main__":
Here we have the common Python if statement <__name__==”__main__”:> which restricts this code to only run when executed directly, not when imported. This allows the reuse of functions in other parts of this program, without having to execute the whole script.
pw = input("Enter your password: ")
password_strength(pw)
Finally, I have made pw a variable and an input() function with instructions for the user to enter their password. The last line calls the password_strength function with the pw variable containing the entered password string.
I learned a lot doing this project, love how simple yet powerful Python is, and can’t wait for my next project!
The full code for this project can be found on my Github.