1
\$\begingroup\$

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()
\$\endgroup\$
2
\$\begingroup\$

(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.

\$\endgroup\$

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.