Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
415: Building a Password Generator
I've seen a lot of junior devs start their first password generator by reaching for the random module. It feels like the obvious choice. You want something random, and there's a module literally called random. But here is the problem: random is a pseudo-random number generator. It's designed for simulations and games, not for security. If an attacker knows exactly when your program started or can guess the "seed" used by the generator, they can predict every single "random" character your program will ever produce.
Thinking random.choice() is secure
Let's look at what a typical "first attempt" looks like. You might write something like this:
import random
import string
chars = string.ascii_letters + string.digits + string.punctuation
password = ''.join(random.choice(chars) for i in range(12))
print(password)
On the surface, this looks perfect. It's concise, it's fast, and the output looks like a password. But because random uses the Mersenne Twister algorithm, it is completely deterministic. If I can see a few outputs from your generator, I can potentially reverse-engineer the internal state and predict the next password you generate. In the world of security, "looks random" isn't the same as "is cryptographically secure."
Using secrets for true unpredictability
Since Python 3.6, we have the secrets module. I always tell my mentees: if the output is going to be used for a password, a token, or a security key, random is banned. Use secrets. It accesses the most secure source of randomness provided by your operating system (like /dev/urandom on Unix).
The transition is actually very easy because the API is almost identical. Here is how we actually build this to be production-ready:
import secrets
import string
def generate_secure_password(length=16):
# Define our character pools
alphabet = string.ascii_letters + string.digits + string.punctuation
# secrets.choice is cryptographically strong
password = ''.join(secrets.choice(alphabet) for i in range(length))
return password
print(generate_secure_password())
Now, we've solved the predictability problem, but we have a new one: probability. If you just pick characters randomly, there is a small (but real) chance your 12-character password contains no numbers or no symbols. Most systems require at least one of each. To fix this, I prefer a "guarantee" approach: pick one character from each required set first, then fill the rest of the length randomly, and finally shuffle the whole thing so the required characters aren't always at the beginning.
Guaranteeing complexity through forced inclusion
To make sure the password actually meets complexity requirements, we can't just hope for the best. We need to be explicit. Check out this pattern:
import secrets
import string
def generate_complex_password(length=12):
if length < 4:
raise ValueError("Password length must be at least 4 to include all required types.")
# 1. Define required sets
sets = [
string.ascii_lowercase,
string.ascii_uppercase,
string.digits,
string.punctuation
]
# 2. Guarantee one character from each set
password = [secrets.choice(s) for s in sets]
# 3. Fill the remaining length from the combined pool
all_chars = "".join(sets)
password += [secrets.choice(all_chars) for _ in range(length - 4)]
# 4. Shuffle the list so the first 4 aren't predictable
# We use secrets.SystemRandom().shuffle because random.shuffle is not secure
secrets.SystemRandom().shuffle(password)
return "".join(password)
print(generate_complex_password(16))
By using secrets.SystemRandom().shuffle(), we keep the entire process cryptographically secure while ensuring the password isn't just random, but also compliant with standard security policies.
📋 Practical Task
Build a "Custom Requirement" Password Generator
Your task is to create a function called generate_custom_password(length, use_digits=True, use_special=True). The generator should behave as follows:
- If
use_digitsis True, the password must contain at least one digit. - If
use_specialis True, the password must contain at least one punctuation character. - The password must always contain at least one lowercase and one uppercase letter.
- It must use the
secretsmodule for all random selections and shuffling. - If the requested
lengthis shorter than the number of required character types, the function should raise aValueError.
Test your function by generating a 12-character password with use_digits=True and use_special=False, and verify that no special characters appear in the output.
There are no comments for now.