Password Generator in Python

 · shakiestnerd
Table of contents

I recently watched a Tech With Tim tutorial on YouTube about creating a little password generator application in python. I followed the tutorial and it worked just fine for me. Tutorial videos like these are mainly intended to help get new users up to speed with a language and give some actual experience with writing and debugging code.

When following things like this, you begin to notice areas where it can be improved. I consider those things as improvement that are "left as an exercise for the viewer." So, in playing around with it I made a few improvements and am posting them here.

Mini Python Project Tutorial - Password Generator

<div class="aspect-w-16 aspect-h-9">
  <iframe
    width="736"
    height="414"
    src="https://www.youtube.com/embed/XCIBOl3FTKo"
    title="Mini Python Project Tutorial - Password Generator"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowfullscreen
  ></iframe>
</div>

Areas for Improvement

  1. Print some extra stuff to the screen to make the program look a little better.
  2. The programs asked for a minimum length. The way the program was written, it would continue to add letters until the requirement for having numbers and special characters was satisfied. So, in some cases, the generated password would be longer the the specified length. So, first change is to get the program to return a random password of the exact length specified by the user.
  3. When gathering user input, allow the "yes" answer to be the default if the user just hits ENTER instead of typing "y" or "n".
  4. Some of the comments were giving Tim grief because he didn't use the "secrets" library instead of random. I've never used "secrets", so I tried that out.
  5. It would be nice if the password was automatically copied to the clipboard.
  6. I wanted to stick with the libraries that are included with the python standard library, and not introduce any 3rd party requirements.

A Sample Run

After making the changes, here is a sample result where I just pressed ENTER to accept all the defaults:

PassGen

The Code

"""Generate a complex password."""
import secrets
import random
import string
import subprocess


def generate_password(min_length, numbers=True, special_characters=True):
    """Generate a random password."""
    letters = string.ascii_letters
    digits = string.digits
    special = string.punctuation

    characters = letters
    if numbers:
        characters += digits

    if special_characters:
        characters += special

    pwd = ""

    # if we are including number or special characters,
    # pre-seed the pwd with at least one.
    if numbers:
        pwd += secrets.choice(digits)
    if special_characters:
        pwd += secrets.choice(special)

    while len(pwd) < min_length:
        new_char = secrets.choice(characters)
        pwd += new_char

    pwd = "".join(random.sample(pwd, len(pwd)))

    return pwd


def get_integer(prompt: str, min_length=13) -> int:
    """Get an integer input."""
    ask = f"{prompt} [{min_length} minimum]: "
    value = input(ask)
    if len(value) == 0:
        result = min_length
    else:
        result = max(int(value), min_length)
    return result


def get_yes_or_no(prompt: str, default="y") -> bool:
    """Get a Y/N input from the user."""
    if default.lower() == "y":
        query = "[Y/n]? "
    elif default.lower() == "n":
        query = "[y/N]? "
    else:
        query = "[y/n]? "

    ask = f"{prompt} {query}"
    value = input(ask).lower()
    if len(value) == 0:
        result = True
    else:
        if value == "y":
            result = True
        else:
            result = False
    return result


def set_clipboard(data: str):
    """Use xclip to write the password to the clipboard."""
    p = subprocess.Popen(["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE)
    if p.stdin is not None:
        p.stdin.write(data.encode())
        p.stdin.close()


def main():
    """Gather user input and call the password generator."""
    print("\n===============[ Password Generator ]================\n")
    min_length = get_integer("Enter the minimum password length")
    has_number = get_yes_or_no("Include numbers")
    has_special = get_yes_or_no("Include special characters")
    pwd = generate_password(min_length, has_number, has_special)
    # print(min_length, has_number, has_special)
    print("=" * 53)
    print("The generated password is:", pwd)
    print("=" * 53)
    set_clipboard(pwd)


if __name__ == "__main__":
    main()

Code Changes

Used the if __name__ == "__main__": construct with a main() function.

Threw in some extra print statements, to improve the look slightly.

Split out the inputs into their own separate functions to allow using ENTER for the default and improving the input checking.

Modified the generate_password() function to automatically inject a number or a special character if they were part of the requirement. This eliminates the need to check for those later.

It switches to the secrets library to fetch the characters.

The downside of that is that a number or special char would always be at the beginning of the password. To work around this, there is an extra line of code that mixes up the characters.

pwd = "".join(random.sample(pwd, len(pwd)))

Had to use random for this, since I didn't see any way to do that with secrets.

The clipboard was a bit of a challenge and I resorted to Stack Overflow to find a solution. The set_clipboard() function will probably only work on linux since it runs the xclip utility in a subprocess to copy the password to the clipboard.

And Now What?

So, when you look at the code, there are always more improvements that could be made. Here are a few more things to think about.

  • Improve the command line interface (CLI) by switching to the click or similar library. Could add arguments to be used in place of the prompts.
  • Improve the look of the CLI by using a library like rich. Could also make a GUI version using tkinter, PySimpleGUI, or customtkinter.
  • Improve the input functions to error trap invalid inputs. Would probably look for a library that handles the input for me. It's one thing to be writing a utility for yourself and a totally different can of worms if your planning on distributing your application.
  • As far as the generate_password function goes, the first thing to know is that I'm not a security expert. The irony of passwords is that the more you try to enforce requirements, the more you limit the randomness. I do know that longer is better. I would feel comfortable using the application to generate random passwords for me, but would use passwords longer than 20 characters.