Random password generator – Python3

In this guide we will teach you to how create a close to true random password generator in Python.

With data leaks being as common as they are, good password management has become a critical part of your online tasks.

While there are great applications such as Last pass out there already, the benefit of having a simple Python script comes from having complete and total control over what is being executed.

This guide expects you to have Python 3.6.4 or higher. If you need help with installing this please visit the official website here

Without further ado, let’s get right into it!

Setting up your environment

A best practice when getting started with Python is your setting up your virtual environment per project. A virtual environment is a place inside your directories where you can set up all your dependencies and versions which are required by the project you’re working on.

Installing PIP

To install our virtual environment we will need to use a Python’s package manager, PIP.

To see if PIP installed successfully, open a terminal and type the following

$ pip help

If you see an output from PIP, then it’s installed correctly. If you do not have PIP installed see below on how to install it.

Installing PIP on Windows

If you installed Python on Windows through the installer you should already have PIP installed. If that’s not the case, you can get PIP by running the following command inside a command prompt

python get-pip.py

Installing PIP on Linux

To install PIP on Linux run the following command

$ apt install python3-pip

Installing PIP on openSUSE

To install PIP on openSUSE run the following command

$ zypper install python3-pip

Installing PIP on Arch Linux

To install PIP on Arch Linux run the following command

$ pacman -S python-pip

Installing PIP on CentOS & RHEL

Installing PIP on CentOS & RHEL is a bit different. PIP isn’t packaged into the official software repositories that CentOS & RHEL provide.

First we need to install the EPEL repository, this one provides the PIP package.

$ yum install epel-release

After that we can install PIP

$ yum install python-pip

Installing virtualenv

We’ve installed PIP, so now we can finally install our virtual environment.

We do so by executing the following command

For Windows
$ pip install virtualenv
For non-Windows
$ pip install --user virtualenv

After we’ve installed virtualenv we can start our Python project. Create a new directory in a location where you want to have your password script, eg. /home/User/RandomPassword/ or on Windows systems, C:\\Users\\Default\\Documents\\RandomPassword.

Open up a terminal inside that directory and type the following command

$ virtualenv ENV

This will create an ENV directory inside our RandomPassword directory. Next, we need to activate our virtual environment. We do so by running the following

$ source ENV/bin/activate

You should see the following inside your command prompt after running the source command

(venv) /path/to/python/project

Notice the (venv) part? This means we activated our virtual environment. All our dependencies will be installed to the ENV directory we just created.

Now we can start working on our script!

Creating our random password generator

Let’s get started by creating a new file inside our RandomPassword directory called pass.py and open it up in your preferred text editor.

We want our script to do change the output of the password depending on the arguments we give to our script. So let’s start with that

#!/usr/bin/env python
# pass.py

import argparse
from distutils import util


def main():
    """Setup the parser by adding available arguments"""
    parser = argparse.ArgumentParser(
        description="Password Generator",
        usage="pass.py\npass.py -l 32\npass.py -lc False\npass.py -s False\npass.py [-h] [-l [LENGTH]] [-lc ["
              "LOWERCASE]] [-uc [UPPERCASE]] [-n [NUMBERS]] [-s [SYMBOLS]]",
        epilog="Thanks for using!"
    )

    args = parser.parse_args()


if __name__ == "__main__":
    main()

If we run this file right now we see that the script doesn’t do much yet. Running it again with the --help flag, so: (venv) python pass.py --help will give us a help message which looks like this

usage: pass.py
pass.py -l 32
pass.py -lc False
pass.py -s False
pass.py [-h] [-l [LENGTH]] [-lc [LOWERCASE]] [-uc [UPPERCASE]] [-n [NUMBERS]] [-s [SYMBOLS]]

Password Generator

optional arguments:
  -h, --help            show this help message and exit
  -l [LENGTH], --length [LENGTH]
                        Length of the password. Defaults to 16. Lower is not
                        recommended
  -lc [LOWERCASE], --lowercase [LOWERCASE]
                        Use lowercase letters during generation. Defaults to
                        True
  -uc [UPPERCASE], --uppercase [UPPERCASE]
                        Use uppercase letters during generation. Defaults to
                        True
  -n [NUMBERS], --numbers [NUMBERS]
                        Use numbers during generation. Defaults to True
  -s [SYMBOLS], --symbols [SYMBOLS]
                        Use symbols during generation. Defaults to True

Thanks for using!

Let’s add some arguments that our script can use and assign them to some variable

#!/usr/bin/env python
# pass.py

import argparse
from distutils import util


def main():
    """Setup the parser by adding available arguments"""
    parser = argparse.ArgumentParser(
        description="Password Generator",
        usage="pass.py\npass.py -l 32\npass.py -lc False\npass.py -s False\npass.py [-h] [-l [LENGTH]] [-lc ["
              "LOWERCASE]] [-uc [UPPERCASE]] [-n [NUMBERS]] [-s [SYMBOLS]]",
        epilog="Thanks for using!"
    )

    parser.add_argument("-l", "--length", nargs='?', const=16, type=int, default=16, help="Length of the password. Defaults to 16. Lower is not recommended")
    parser.add_argument("-lc", "--lowercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use lowercase letters during generation. Defaults to True")
    parser.add_argument("-uc", "--uppercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use uppercase letters during generation. Defaults to True")
    parser.add_argument("-n", "--numbers", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use numbers during generation. Defaults to True")
    parser.add_argument("-s", "--symbols", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use symbols during generation. Defaults to True")

    args = parser.parse_args()

    l = args.length
    lc = args.lowercase
    up = args.uppercase
    n = args.numbers
    s = args.symbols

    print(str(l) + "\n")
    print(str(lc) + "\n")
    print(str(up) + "\n")
    print(str(n) + "\n")
    print(str(s) + "\n")


if __name__ == "__main__":
    main()

If we run our script now we should be greeted with the following output

16

True

True

True

True

Let’s try disabling symbols with (venv) python pass.py -s False

16

True

True

True

False

Cool! Our arguments appear to be working correctly. Now it’s time to generate a password!

#!/usr/bin/env python
# pass.py
import argparse
import secrets
import random
import string
import distutils
from distutils import util


def r_pass(length, letters):
    secretsGenerator = secrets.SystemRandom()
    letters = letters
    return ''.join(letters[secretsGenerator.randint(0, len(letters) - 1)] for i in range(length))


def main():
    """Setup the parser by adding available arguments"""
    parser = argparse.ArgumentParser(
        description="Password Generator",
        usage="pass.py\npass.py -l 32\npass.py -lc False\npass.py -s False\npass.py [-h] [-l [LENGTH]] [-lc ["
              "LOWERCASE]] [-uc [UPPERCASE]] [-n [NUMBERS]] [-s [SYMBOLS]]",
        epilog="Thanks for using!"
    )
    parser.add_argument("-l", "--length", nargs='?', const=16, type=int, default=16, help="Length of the password. "
                                                                                          "Defaults to 16. Lower is "
                                                                                          "not recommended")
    parser.add_argument("-lc", "--lowercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use lowercase letters during generation. Defaults to True")
    parser.add_argument("-uc", "--uppercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use uppercase letters during generation. Defaults to True")
    parser.add_argument("-n", "--numbers", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use numbers during generation. Defaults to True")
    parser.add_argument("-s", "--symbols", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use symbols during generation. Defaults to True")

    args = parser.parse_args()

    l = args.length
    lc = args.lowercase
    up = args.uppercase
    n = args.numbers
    s = args.symbols

    letters = ""
    letters += string.ascii_lowercase if lc else ""
    letters += string.ascii_uppercase if up else ""
    letters += "0123456789" if n else ""
    letters += "!@#$%^&*()_+-={}[]<>,.;:/~" if s else ""

    res = r_pass(l, letters)
    print(res)


if __name__ == "__main__":
    main()

What we’ve done here is add a string of characters called letters. We append different groups of characters depending on whether the passed argument returns True or False.

We then pass our length argument and our final letters string to a function called r_pass. This function does the random generation and noise magic. We use a library called secrets for the noise and randomness.

This function returns a random character from the letters we provided by passing arguments. It does this until we’ve hit the limit of argument l length

If we run our code now we should see an output resembling something like

(venv) python pass.py
>[H}%}h*_mfAj(j6

That’s all there is to it. You’ve now made your own random password generator!

Optional – Place in clipboard

Want to know how we can make this generator even more fancy? We can make it return our password directly into our computer’s clipboard. We do this by updating our main() function and by creating another function called module_exists(module_name)

Our final code becomes

#!/usr/bin/env python
# pass.py
import argparse
import secrets
import random
import string
import distutils
from distutils import util


def r_pass(length, letters):
    secretsGenerator = secrets.SystemRandom()
    # letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-={}[]<>,.;:/~"
    letters = letters
    # return ''.join(random.choice(letters) for i in range(length))
    return ''.join(letters[secretsGenerator.randint(0, len(letters) - 1)] for i in range(length))


def main():
    """Setup the parser by adding available arguments"""
    parser = argparse.ArgumentParser(
        description="Password Generator",
        usage="pass.py\npass.py -l 32\npass.py -lc False\npass.py -s False\npass.py [-h] [-l [LENGTH]] [-lc ["
              "LOWERCASE]] [-uc [UPPERCASE]] [-n [NUMBERS]] [-s [SYMBOLS]]",
        epilog="Thanks for using!"
    )
    parser.add_argument("-l", "--length", nargs='?', const=16, type=int, default=16, help="Length of the password. Defaults to 16. Lower is not recommended")
    parser.add_argument("-lc", "--lowercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use lowercase letters during generation. Defaults to True")
    parser.add_argument("-uc", "--uppercase", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use uppercase letters during generation. Defaults to True")
    parser.add_argument("-n", "--numbers", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use numbers during generation. Defaults to True")
    parser.add_argument("-s", "--symbols", nargs='?', const=True, type=lambda x: bool(distutils.util.strtobool(x)),
                        default=True, help="Use symbols during generation. Defaults to True")

    args = parser.parse_args()

    l = args.length
    lc = args.lowercase
    up = args.uppercase
    n = args.numbers
    s = args.symbols

    letters = ""
    letters += string.ascii_lowercase if lc else ""
    letters += string.ascii_uppercase if up else ""
    letters += "0123456789" if n else ""
    letters += "!@#$%^&*()_+-={}[]<>,.;:/~" if s else ""

    res = r_pass(l, letters)
    if module_exists("pyperclip"):
        pyperclip.copy(res)
        print("Password has been copied to your clipboard")
    else:
        print(res)


def module_exists(module_name):
    try:
        __import__(module_name)
    except ImportError:
        return False
    else:
        return True


if __name__ == "__main__":
    main()

What we do now after we’ve generated our password is check whether or not the module pyperclip exists. If it does we paste our password to the clipboard. If it does not, the script prints the password into the console like before.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *