# Checking the strength of a password using regexes

I'm a beginner in Python and I made a password checker. I'm following these rules:

• I have to use regex
• password must be at least 8 characters
• must have at least one lower case character
• must have at least one upper case character
• must have at least one special character
• must have at least one number

I also wanted to display a message letting the user know what conditions weren't met.

I'm a bit concerned by the succession of ifs.

#strong password checks
import re

#Error message to be
Err_mess = ''

#first regex : at least one lower case characters
pw_lc = re.compile(r'([a-z]+)')

#second regex : at least one upper case characters
pw_uc = re.compile(r'([A-Z]+)')

#third regex: at least one special character
pw_sc = re.compile(r'([\-_\?!@#$%^&\*]+)') #fourth regex : password must have at least one number pw_d = re.compile(r'([0-9])+') #create a list to be given to all() to evaluate if all conditions are met or not cond_pw = [len(pwtocheck) > 8, pw_lc.search(pwtocheck), pw_uc.search(pwtocheck), pw_sc.search(pwtocheck), pw_d.search(pwtocheck)] #building an error message #is the password at least 8 characters if len(pwtocheck) < 8: Err_mess = Err_mess + 'password is too short\n' #first regex is none if pw_lc.search(pwtocheck) is None: Err_mess = Err_mess + 'password needs at least one lower case character\n' #second regex is None if pw_uc.search(pwtocheck) is None: Err_mess = Err_mess + 'password needs at least one upper case character\n' #third regex is None if pw_sc.search(pwtocheck) is None: Err_mess = Err_mess + 'password needs at least one special character\n' #fourth regex is none if pw_d.search(pwtocheck) is None: Err_mess = Err_mess + 'password needs at least one number\n' if all (cond_pw) is True: print('password is ok') else: print(Err_mess)  ## 2 Answers ## Concept Current security research recommends eliminating character composition requirements. It's more important to blacklist common weak passwords. Also, passphrases are a good alternative to passwords. For the purposes of this code review, though, I'll play along with your original premise. ## Style PEP 8, the official Python style guide, recommends lower_case_with_underscores for variable names. Err_mess violates that guideline for no good reason. ## Regexes You don't need the capturing parentheses, if you're not going to do anything with the capture groups. You also don't need the + modifiers, since the requirement is for just one character of each class. Within a character class (inside square brackets), most characters lose their special meanings. You don't need to escape ? and * with backslashes. ## Design You're performing each check twice: once to check the password validity, and a second time to gather all of the failure reasons. This code suffers from the fact that the rules are encoded in multiple places: • the regular expressions (and their variable names) • the comments • the if statements (note especially the length check, which breaks the pattern) • the error message strings It would be better to treat all of the rules as data, uniformly, and have a single statement that performs all of the checks. import re pw_to_check = input('please enter the password to be checked:') rules = [ ('password is too short', r'.{8}'), ('password needs at least one lower case character', r'[a-z]'), ('password needs at least one upper case character', r'[A-Z]'), ('password needs at least one special character', r'[-_?!@#$%^&*]'),
('password needs at least one number', r'[0-9]'),
]

err_msg = '\n'.join(
req
for req, regex in rules
if not re.search(regex, pw_to_check)
)



I'm guessing that the expression you wish to design, in a final/combined form, would somewhat look like,

^(?=.*[a-z])(?=.*[A-Z])(?=.*[?!@#$%^&*_-])(?=.*[0-9])[A-Za-z0-9?!@#$%^&*_-]{8}$ for instance, which of-course can be likely simplified. Here, we are using four positive lookaheads to check for those at least conditions, anywhere in the 8 chars. We probably don't want to escape some metachars inside the character class [], usually we would include - at the end, which is used as a range inside a char class such as a-z, A-Z and A-z. The expression is explained on the top right panel of regex101.com, if you wish to explore/simplify/modify it, and in this link, you can watch how it would match against some sample inputs, if you like. ### Test with re.findall import re regex = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[?!@#$%^&*_-])(?=.*[0-9])[A-Za-z0-9?!@#$%^&*_-]{8}$"

test_str = """
aB&9aaaa
aB&9aaaaa
aB&9aaa
aB&9xy8a
"""

print(re.findall(regex, test_str, re.MULTILINE))


### Output

['aB&9aaaa', 'aB&9xy8a']


### Test with re.finditer

# coding=utf8
# the above tag defines encoding for this document and is for Python 2.x compatibility

import re

regex = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[?!@#$%^&*_-])(?=.*[0-9])[A-Za-z0-9?!@#$%^&*_-]{8}\$"

test_str = """
aB&9aaaa
aB&9aaaaa
aB&9aaa
aB&9xy8a
"""

matches = re.finditer(regex, test_str, re.MULTILINE)

for matchNum, match in enumerate(matches, start=1):

print ("Match {matchNum} was found at {start}-{end}: {match}".format(matchNum = matchNum, start = match.start(), end = match.end(), match = match.group()))

for groupNum in range(0, len(match.groups())):
groupNum = groupNum + 1

print ("Group {groupNum} found at {start}-{end}: {group}".format(groupNum = groupNum, start = match.start(groupNum), end = match.end(groupNum), group = match.group(groupNum)))

# Note: for Python 2.7 compatibility, use ur"" to prefix the regex and u"" to prefix the test string and substitution.


### Output

Match 1 was found at 1-9: aB&9aaaa
Match 2 was found at 28-36: aB&9xy8a


### RegEx Circuit

jex.im visualizes regular expressions:

### Reference

Regex for password must contain at least eight characters, at least one number and both lower and uppercase letters and special characters

• Using four separate regular expressions keeps the code as readable as possible. Reading your combined regular expression is much more difficult. – Roland Illig Jul 20 at 7:49