Password, please!

devarno
post-background
Simplifying security with FastAPI
<-return_to_blog/>
$_password
$_security
$_fastapi
$_python
$_webdev

Tying a (secure) knot

2024 has certainly been different.

Among the many curveballs of the year so far, watching most of my university friends getting married has been nothing short of exceptional - both to witness and be a part of. Big shoutout to all involved; you know who you are.

Now, onto the bachelor parties...

I found myself in some interesting conversations over the countless stag weekends. One topic that came up surprisingly often? Passwords.

Many of my friends confessed to using the same password for most of their accounts (if you're nodding along, you’re in the right place).

While it might make life a tad easier, recycling the same password is a cyber-culprit's dream come true.

One of the most common pain points people face when creating passwords is the eternal struggle between memorability and security. We've all been there: you're setting up a new account, and the temptation to reuse a snappy, memorable password is strong. It's the password equivalent of comfort food - a familiar choice that's easy to remember but, unfortunately, just as easy to compromise.

We've all heard about the dark side of the internet and the lurking threats. It's almost 2025, and while we may not have flux capacitors or flying cars just yet, we do have advancements like smart homes, AI assistants, and self-driving vehicles.

These innovations highlight the need for some very real security considerations:

1. The Key Problem

Imagine a thief trying to gain entry to a house by trying every key in existence. That's essentially what a brute force attack is - trying different combinations until the right one is found.

A short password like 12345 can be cracked almost instantly. A ten-digit password has 17,000 times the possible combinations that a six-digit one does, drastically increasing security.

Short and simple passwords can (and will) fall like dominoes.

2. Longer is Stronger

Longer and complex passwords take vastly more time and computational power to crack.

For example, according to research, a seven-character alphabetical password could be cracked in 50 minutes (or less).

Extend that to ten characters, and the time shoots up to two years.

Make it twelve, and you’re looking at roughly one thousands years (seriously).

3. Ditch Predictable Patterns

Many people opt for familiar words or phrases in their passwords, but hackers anticipate this and use targeted word lists to crack them. Creating a unique, random password helps you dodge this predictability trap.

Using something like your childhood pet's name? Your favourite food? The town you grew up in?

That's exactly the kind of pattern hackers eat for breakfast. Keep them guessing with unpredictability.

With all that said, this is the perfect opportunity to pull out this table I’m sure you’ve seen before.

Behold!

password_statistics.png{caption: Hive Systems Password Table (2024)}

Hive Systems Password Table (2024)

Those are some pretty startling numbers, right?

Instead of losing sleep over the consequences, let's implement a solution.

Password, please!

Let me introduce you to Password, please! (pwplz ) - a slick new tool I've launched at pwplz.com that's here to take the stress out of creating secure passwords.

pwplz provides instant access to a variety of secure passwords, which can be easily managed if you choose to embrace the right tools and ditch the hand-written notes. Copy your preferred password and paste it directly into your password manager or file.

PasswordGenerator

This application is a fantastic example of how simple yet powerful coding can be, making it an excellent entry point for anyone interested in coding and programming. At its heart is the PasswordGenerator, a straightforward yet versatile class with various methods for generating different types of passwords:

1. Random

Uses a mix of letters, numbers, and symbols to create a password that’s hard to predict. Super secure with a good mix of characters that can fend off brute-force attacks, but they’re tough to remember without a password manager. Python's secrets module helps us generate these strong, random passwords by picking a combination of letters, digits, and symbols:

1import secrets, itertools 2 3def _create_random_password(self) -> str: 4 """Generate a password using random sampling""" 5 password_chars = [secrets.choice(charset) for charset in self.charsets] 6 permitted_chars = ''.join(itertools.chain(*self.charsets)) 7 chars_left = self.min_length - len(password_chars) 8 for _ in range(chars_left): 9 password_chars += [secrets.choice(permitted_chars)] 10 secrets.SystemRandom().shuffle(password_chars) 11 password = ''.join(password_chars) 12 return password 13 14 15# JAnp"m9A2%9f?+3oqf@PJ@m~z3]!W9-k73K}+';<]<iJ*>1eBuV|U1O\"kJ%Csk3hXr^ynI,/=l1&q1g 16
2. Argon2

A modern encryption technique that ensures your passwords are securely hashed, adding an extra layer of protection. While Argon gives top-notch security against side-channel and brute-force attacks, it can be slow and not always supported everywhere. In Python, the argon2 package lets us hash passwords securely using this robust, memory-hard algorithm:

1from argon2 import PasswordHasher 2 3def _create_argon2_password(self) -> str: 4 """Generate a password using Argon2""" 5 hasher = PasswordHasher() 6 password = hasher.hash(secrets.token_hex(32)) 7 return password[:self.min_length] 8 9# $argon2id$v=19$m=65536,t=3,p=4$A7wXz8AZW6PqnykpAkFRTQ$aUVbqSaPIMcOcf/bX15wMuHxZQ 10
3. Diceware

Combines random words to form a longer passphrase that is both secure and somewhat easier to remember, but tends to be lengthy and not ideal for systems with short password limits. In Python, we simply pick words randomly from a list, making long yet easier-to-recall passphrases:

1def _create_diceware_password(self) -> str: 2 """Generate a Diceware-based password""" 3 password = "" 4 wordlist = self._get_random_words() 5 if not wordlist: 6 return "" 7 while len(password) < self.min_length: 8 password += secrets.choice(wordlist) + '-' 9 return password.rstrip('-') 10 11# tambourine-kiosk-helium-willow-quilt-keychain-snowman-insect-daffodil-dragonfly 12
4. UUID

These universally unique identifiers, which are long strings that are unique to each generation, are great for ensuring uniqueness, but lack the complexity for high-security needs. In Python, the uuid module creates these identifiers, offering uniqueness with a bit of predictability:

1import uuid 2 3def _create_uuid_password(self) -> uuid.UUID: 4 """Generate a Universally Unique Identifier password""" 5 return str(uuid.uuid4()) 6 7# 369d3e98-6ea8-4016-a4ea-9f4554477045 8
5. Hash

Uses cryptographic hashing like SHA-256 to turn input into secure, irreversible passwords, but their strength hinges on using a strong seed and salt. In Python, this involves digesting input data with hashlib to generate a secure password:

1import secrets, hashlib 2 3def _create_hash_password(self) -> str: 4 """Generate a password using a hash function""" 5 random_string = secrets.token_bytes(16) 6 hash_digest = hashlib.sha256(random_string).hexdigest() 7 return hash_digest 8 9# 465289f8ab6c1d249c13a023d07d6c8be759601eee7af2e0a0340747ea727567 10
6. Bcrypt

A go-to for secure password hashing with an adjustable work factor that ups the difficulty over time, but it's slower and may not suit high-speed environments. In Python, the bcrypt package is used to create hashes, ensuring they're tough against brute-force attacks:

1import bcrypt, secrets 2 3def _create_bcrypt_password(self) -> str: 4 """Generate a salted and hashed password with bcrypt""" 5 salt = bcrypt.gensalt() 6 password = bcrypt.hashpw(secrets.token_bytes(16), salt) 7 password = password.decode("utf-8", "ignore") 8 return password 9 10# $2b$12$XTCbeMQU6S1yEIK78QJPoOlYLqATJS68I1GFd/lI6mpqtjPSfI5WS 11

Given our class methods, we can define an Enum to restrict the type of password that a user can request:

1from enum import Enum 2 3class PasswordType(Enum): 4 RANDOM="random" 5 ARGON2="argon2" 6 DICEWARE="diceware" 7 UUID="uuid" 8 HASH="hash" 9 BCRYPT="bcrypt" 10

The PasswordGenerator class then is initialised with keyword arguments that must include:

  • min_length: the minimum password length required, where applicable
  • words: a hyphen-joined list of words for Diceware passwords.

These can (and should) be configured separately within a .env file, like so:

1MIN_LENGTH=50 2DICEWARE_WORDS=kiwi-apple-orange-banana-watermelon 3

and then passed to the PasswordGenerator upon initialisation:

1import os 2 3if __name__ == "__main__": 4 MIN_LENGTH = int(os.getenv("MIN_LENGTH")) 5 DICEWARE_WORDS = os.getenv("DICEWARE_WORDS") 6 generator = PasswordGenerator(min_length=MIN_LENGTH, words=DICEWARE_WORDS) 7

If you're completely new to Python, the if __name__ == "__main__": block lets you run code only when the file is executed directly, and not when imported as a module. Think of this block as location for any code that should execute solely when your script runs on its own (e.g. python3 my_file.py). Check out this resource for more clarity.

FastAPI

The magic of this app lies in its straightforward simplicity, with FastAPI as the key player making everything work seamlessly. I was eager to craft a project that highlights just how amazing Python can be, and FastAPI's approachable nature made it all come together effortlessly.

Like Flask, Django, and Tornado, FastAPI lets you effortlessly switch up response types. You can easily craft a sleek HTML frontend that syncs seamlessly with an ultra-responsive backend, enabling developers to deliver features at lightning-fast speeds. This post certainly isn't to downplay the power of other frameworks - each shines in different contexts and technical requirements (stay tuned for another app and post soon).

pwplz uses FastAPI for processing requests for password generation, dynamically rendering HTML templates with the generated data, and delivering them back to the user swiftly via one of two endpoints:

1. Render All Passwords

Method: GET /

This endpoint is responsible for displaying all generated passwords using the render_all_passwords endpoint function. When a user visits this route, the application generates a series of passwords using the different methods available in the PasswordGenerator class. It then serves the all_passwords.html template, filling it with these passwords and a randomly chosen password method to highlight the method-specific route capability.

1#-=-=-=-=-=-=-=-=-=-=-=-=> 2# Default route 3@app.get("/", response_class=HTMLResponse) 4#-=-=-=-=-=-=-=-=-=-=-=-=> 5 6async def render_all_passwords(request: Request): 7 """Endpoint to render a simple HTML page with all generated passwords""" 8 try: 9 return templates.TemplateResponse( 10 request=request, 11 name="all_passwords.html", 12 context={ 13 "passwords": generator._generate_all_passwords(), # Content 14 "random_method": generator._get_random_password_type(), # Link 15 } 16 ) 17 except Exception: 18 raise HTTPException( 19 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 20 detail=f"Something went wrong, try again later", 21 ) 22 23

This endpoint allows us to generate and retrieve all password types at pwplz.com.

pwplz_all_passwords.png{caption: All Passwords Demo (pwplz.com)}

All Passwords Demo (pwplz.com)

2. Render Single Password

Method: GET /{slug}

This endpoint handles requests to generate a password using a specific method, with {slug} acting as a placeholder for the type of password the user wants to create (e.g., random or argon2, defined by PasswordType(Enum)). The render_single_password endpoint function checks the method provided in the URL path and retrieves the corresponding password creation method from the PasswordGenerator. It then generates a password using this method and returns the single_password.html template, populated with the generated password.

1#-=-=-=-=-=-=-=-=-=-=-=-=> 2# Method-specific route 3@app.get("/{slug}", response_class=HTMLResponse) 4#-=-=-=-=-=-=-=-=-=-=-=-=> 5 6async def render_single_password(request: Request, slug: str): 7 """Endpoint to render a simple HTML page with a single generated password""" 8 try: 9 # Validate slug in PasswordType 10 password_type = PasswordType[slug.upper()] 11 12 # Map slug to password creation method 13 creator = generator._get_password_creation_method(password_type) 14 15 # Return the template with the generated password 16 return templates.TemplateResponse( 17 request=request, 18 name="single_password.html", 19 context={ 20 "slug": slug, 21 "password": creator(), 22 }, 23 ) 24 except KeyError: 25 raise HTTPException( 26 status_code=status.HTTP_400_BAD_REQUEST, 27 detail=f"Invalid password type: {slug}", 28 ) 29

This dynamic route generates a single password based on a specific PasswordType, which is defined within the URL (e.g. pwplz.com/random).

pwplz_single_passwords.png{caption: Single Password Demo (pwplz.com/random)}

Single Password Demo (pwplz.com/random)

The Inner Workings

If, like me, your brain processes information better through the sacred language of boxes and arrows (because words are overrated), here’s a block diagram of the pwplz application, illustrating its elegant dance from request to response.

pwplz_blocks.png

Block Diagram of pwplz Application

This diagram highlights how pwplz uses environment configurations to set up the PasswordGenerator, processes requests through the FastAPI app, and handles responses and errors to ensure everything runs smoothly. On the frontend, responses are rendered in HTML templates, providing feedback to users, while backend processes manage password creation, configuration, and error control.

Getting involved

While it’s all set for you to use without fuss, developers are welcome to dive in and contribute. If you're interested in tweaking or suggesting features, please feel free to open a pull request.

Even without a technical background, by exploring this app you’ll get a clearer picture of how password security works, and discover the fun and satisfaction of creating something through programming (and Python is a perfect starting point).

Dive in, check out the code, and see how just a little programming know-how can make a big difference.

To start contributing, check out the GitHub repository.

Your support

pwplz is an open-source project, and pwplz.com will remain free forever.

However, if you enjoy the apps I create, the content I share, or my Indie Hacker journey, consider supporting my work with a donation. Your support keeps this Indie adventure thriving.

Also, please feel free to follow me on my upcoming digital adventures so you don’t miss out on some exclusive early-bird perks.

Or just sit back and enjoy watching the coding chaos unfold from a safe distance.

Live long and program

All jokes aside, please remember to stay safe online; protect your digital assets, and never again fall trap to the easy password syndrome.

Remember, a strong password is like a bouncer at the door of your digital life (not someone you want to skimp on).

For any collaborations, technical advice or feedback, feel free to hit me up at devarno.com or email me directly.

Cheers to surfing the web securely, and may the {source} be with you 🧑🏻‍💻