I wanted to learn some regex, but learning regex is super boring so I decided to write a password validator that will validate a password with regular expressions.
How it works:
- You enter a password that has no echo thanks to the
getpass
library - The password is run against five different validations such as, upper case, lower case, special character, and digits. These are done by using the
re
(regex) library. - If the password does not pass one of the validations it will output a random password using the
random
library that will match the given guidelines.
What I would like to know is:
- Are there better ways to create a random string?
- Are there better regular expressions I could use to verify that password?
Source:
import getpass
import re
import random
def random_password(length=3):
""" Create a a random password to display if the
primary validation fails. """
valid_upcase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
valid_lowcase = 'abcdefghijklmnopqrstuvwxyz'
valid_specs = '!$@&'
valid_digits = '1234567890'
return ''.join((random.choice(valid_upcase) + random.choice(valid_lowcase) + random.choice(valid_specs) + random.choice(valid_digits) for i in xrange(length)))
def length_error(password):
""" Validate that the password is over 8 characters
and no more than 20 characters. """
if 8 > len(password) :
return False
else:
return True
def lower_error(password):
""" Confirm that the password contains at least one
lower case letter """
if re.search(r"[a-z{1,9}]", password) is None:
return False
else:
return True
def symbol_error(password):
""" Make sure that the password contains at least one
of the following special characters: ! @ $ & """
if re.search(r"[!@$&{1,5}]", password) is None:
return False
else:
return True
def upcase_error(password):
""" Confirm that the password contains at least one
upper case character. """
if re.search(r"[A-Z{1,5}]", password) is None:
return False
else:
return True
def digit_error(password):
""" Confirm that the password contains at least one
digit. """
if re.search(r"\d{1,5}", password) is None:
return False
else:
return True
def prompt(info):
""" Get the password without echo. """
return getpass.getpass(info)
#return raw_input(info) # # Uncomment this for echo
def validate_password(password):
""" Where the validation occurs, if the password does
not pass one of the following tests, it will output
a random string that does pass the test. """
if lower_error(password) is False or upcase_error(password) is False:
print "Password did not match the uppercase or lowercase requirements."
print "Random password to use: {}".format(random_password())
elif digit_error(password) is False or symbol_error(password) is False:
print "Password did not match the integer and special character requirements."
print "Random password to use: {}".format(random_password())
elif length_error(password) is False:
print "Password did not meet the length requirements."
print "Password must be over 8 characters and no more then 20 characters"
print "Random password to use: {}".format(random_password())
else:
print "Password validated successfully."
def obtain_password():
""" Main method. """
password_to_verify = prompt("Enter password: ")
validate_password(password_to_verify)
if __name__ == '__main__':
obtain_password()
1 Answer 1
(Note: Python is not my main language, and this is my first answer on Code Review)
Looks alright, I can't see any obvious holes in the regex. Tried it a few times myself, worked as expected.
I don't really have a good answer for you "random string" question, but for:
- Are there better regular expressions I could use to verify that password?
Well, I think you could take out the curly brackets out on them. For instance, when you're matching any lower case letter, at least once, you have:
if re.search(r"[a-z{1,9}]", password) is None:
Since you only want to know if one exists, you don't need the {1,9}
. So, unless there's some strangeness with re.search
that I don't know about, you could replace [a-z{1,9}]
with just [a-z]
. Ditto for the other validation functions.
Now, I'm not 100% certain with this, but it seems you could return either None
or a number from your validation functions, and test whether it's equal to None
in validate_password()
. Like this:
def lower_error(password):
return re.search(r"[a-z]", password)
def symbol_error(password):
return re.search(r"[!@$&]", password)
def upcase_error(password):
return re.search(r"[A-Z]", password)
def digit_error(password):
return re.search(r"\d", password)
Now lower_error()
etc return None, or a number.
def validate_password(password):
if lower_error(password) is None or upcase_error(password) is None:
...
elif digit_error(password) is None or symbol_error(password) is None:
...
I was a bit worried about 0 being equal to None
, but typing 0 is None
into the Python REPL says False
.
EDIT: By the way, just noticed length_error()
only checks if it's less than 8 characters, not that it's under 20.