DEV Community

Cover image for Custom Password Validation in Python (Function for password validation)
Michael Otu
Michael Otu

Posted on • Updated on

Custom Password Validation in Python (Function for password validation)

These posts will be in three parts.

  1. Function for password validation
  2. Refactoring the function for password validation
  3. Unit test the function for password validation

Who cares about custom password validation? If some libraries/modules can effortlessly validate our passwords, is there a need to (re)write one? Why?

What do we say is a strong password? How is a strong password structured? Can we define a strong password? Are strong passwords even important? Is the security of our application any better with secure passwords?

The purpose of asking these questions is so that we can all, at least, be on the same page - agree that we need secure and safe passwords. I am not a software security expert or engineer, so don't trust me.

The Structure Of A Secure Password

As I said earlier, "I am not a software security expert or engineer, so don't trust me". I meant it. Since the idea of the structure of a password may be a preference or based on requirements, I think that at a point, we should all agree that a password should (or must) satisfy most of these conditions. (These are my conditions, you are free to add and remove).

  • No leading or trailing white spaces - so we strip them off
  • Should be between 6 to 20 characters long
  • Must exclude any of these, %;`<>\/{}[]+=?&,:'" and blank space
  • Must have at least one special symbol. !@#$^&()_.-.
  • Must have at least one number.
  • Must have at least one lowercase character.
  • Must have at least one uppercase character.

We will implement a function to take care of each of these conditions.

The Function

Usually, it is hard for developers to name "things" in their code. I add a keyword from the function description to the function name. Our function validates a password. We can name it a password_validator. password_validator is a noun. We need a verb. validate or validate_password will do. We expect a bool from the function. is_valid or is_valid_password sounds "ok". The latter sounds better with a context, which is alright since we are using a function and not a class. The former would be better if we were to implement a class. From here on our function name is is_valid_password.

Function Definition

def is_valid_password(password):
    pass
Enter fullscreen mode Exit fullscreen mode

We would start with this function definition without a body/implementation. The pass keyword means we would implement the body later.

No Trailing Spaces

In a form, there maybe be a chance that the submitted data has some space with it. newline, which is enter may submit the form. tab, would go to the next input field. Space will just add space so we have to strip/trim it. In python, we use the strip method to remove spaces from the password (a password is a string object).

def is_valid_password(password):
    new_password = password.strip()
Enter fullscreen mode Exit fullscreen mode

As you can see, the password variable (parameter) was not modified. This is a good practice not to directly modify data passed as an argument.

Must Be Between 6 To 20 Characters Long

Does the size of the password matter, security-wise? Yes, it matters a lot. Sat we have a one-character-password. This password is limited to the alphabetic letters in the range, [A-Za-z]. Guess how long it will take a python script to crack it?

I would say that a password should be between 6 to 20 characters long. I believe 8 should be a minimum. The only issue here is that the user must remember a sequence of 8 or so characters for a password.

def is_valid_password(password):
    ...

    if len(new_password) < 6 or len(new_password) > 20:
        return False
Enter fullscreen mode Exit fullscreen mode

In the above snippet, if the password length is less than six or twenty characters, the function returns False. Now we have two magic numbers, numerical literals, we can easily replace them with some constant variables.

Which name will be better, MIN_SIZE or MIN_PASSWORD_SIZE? The latter has a context. It makes it a better name in my opinion.

The is_valid_password function name itself has a context so in a way, we don't have to be "sticky" with "contexting". It makes MIN_SIZE a more suitable option.

def is_valid_password(password):
    ...

    MIN_SIZE = 6
    MAX_SIZE = 20
    password_size = len(new_password)

    if password_size < MIN_SIZE or password_size > MAX_SIZE:
        return False
Enter fullscreen mode Exit fullscreen mode

Must Have A Special Character

There are characters other than letters and numbers that should also be included and excluded from a password.

Characters to:

  • include: !@#$^&()_.-
  • exclude: %;`<>\/{}[]+=?&,:'"

All other symbols not accounted for should be excluded from the special characters. We would use some import from the string module.

from string import punctuation, whitespace

def is_valid_password(password):
    ...

    valid_chars = {'-', '_', '.', '!', '@', '#', '$', '^', '&', '(', ')'}
    invalid_chars = set(punctuation + whitespace) - valid_chars
Enter fullscreen mode Exit fullscreen mode

To account for the other symbols to exclude, we added punctuations and whitespace then we remove all valid characters. Now we have invalid characters. If any of the invalid characters are found in the password passed, then the password is invalid so we return False.

from string import punctuation, whitespace

def is_valid_password(password):
    ...

    for char in invalid_chars:
        if char in new_password:
            return False
Enter fullscreen mode Exit fullscreen mode

Must Have At Least One Number

It is not bad to have a number in a password. Let's check if the password has a number. We would import digits from the string module.

from string import digits

def is_valid_password(password):
    ...

    password_has_digit = False

    for char in password:
        if char in digits:
            password_has_digit = True
            break

    if not password_has_digit:
        return False
Enter fullscreen mode Exit fullscreen mode

We set a flag (something that can be turned/switched on/off), password_has_digit and we set it to False. We assume that the password has no number in it. We loop through the password and check if a character in the password is a number. If a character is a number, we set password_has_digit to True and we break from the loop. Finally, we check if the flag we set is False to return False from the function as a password without a number is invalid (in our case).

Must Have At Least One Lower and Upper Case Character

Keystrokes from the keyboard are lower cases. We either have to hit the caps lock or hold down the shift key to enter an upper case character.

Let's check for a lower case character in our password. We can manually enter the lower case characters into a string or list but we can import them from the string module. We did the same for digits, punctuation and whitespace.

from string import ascii_lowercase, ascii_uppercase

def is_valid_password(password):
    ...

    password_has_lowercase = False

    for char in password:
        if char in ascii_lowercase:
            password_has_lowercase = True
            break

    if not password_has_lowercase:
        return False


    password_has_uppercase = False

    for char in password:
        if char in ascii_uppercase:
            password_has_uppercase = True
            break

    if not password_has_uppercase:
        return False
Enter fullscreen mode Exit fullscreen mode

Finally

We will return True when these requirements are met.

def is_valid_password(password):
    ...

    return True
Enter fullscreen mode Exit fullscreen mode

Conclusion

Our code will like the snippet below when we assemble it.

from string import (
    punctuation, whitespace, digits,
    ascii_lowercase, ascii_uppercase)


def is_valid_password(password):
    new_password = password.strip()

    MIN_SIZE = 6
    MAX_SIZE = 20
    password_size = len(new_password)

    if password_size < MIN_SIZE or password_size > MAX_SIZE:
        return False

    valid_chars = {'-', '_', '.', '!', '@', '#', '$', '^', '&', '(', ')'}
    invalid_chars = set(punctuation + whitespace) - valid_chars

    for char in invalid_chars:
        if char in new_password:
            return False

    password_has_digit = False

    for char in password:
        if char in digits:
            password_has_digit = True
            break

    if not password_has_digit:
        return False

    password_has_lowercase = False

    for char in password:
        if char in ascii_lowercase:
            password_has_lowercase = True
            break

    if not password_has_lowercase:
        return False


    password_has_uppercase = False

    for char in password:
        if char in ascii_uppercase:
            password_has_uppercase = True
            break

    if not password_has_uppercase:
        return False

    return True
Enter fullscreen mode Exit fullscreen mode

You better cross your fingers. It is not battle-tested. We would get into details and reveal some bugs in the second part of the post. The next post will be on Refactoring the function for password validation.

Top comments (0)