3
\$\begingroup\$

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

#ask the user for the password to check
pwtocheck = input('please enter the password to be checked:')

#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)
\$\endgroup\$
3
\$\begingroup\$

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)
)

print(err_msg or 'password is ok')
\$\endgroup\$
1
\$\begingroup\$

Your code looks great!

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:

enter image description here

Reference

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

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.