-
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
254: The secrets Module for Cryptographic Randomness
Imagine you're watching a street magician perform a "random" card trick. To the crowd, the card he picks seems totally unpredictable. But if you're a fellow magician, you might notice he's using a "stacked deck"—a specific sequence of cards that looks random but is actually a predetermined pattern. If you know the pattern (the seed), you know exactly which card is coming next. This is exactly how the random module works in Python. It's great for simulations or games, but it's a disaster for security because it's deterministic.
Now, compare that to a high-end casino's shuffling machine. It uses physical entropy—true chaos—to ensure that no one, not even the house, can predict the next card. That's what the secrets module does. It taps into your operating system's highest quality source of randomness to give you numbers that are cryptographically strong. In other words, there's no "stacked deck" here.
Stop using random for security
I can't stress this enough: if you are generating anything that needs to be unguessable—like a password, a session cookie, or a password reset token—stop using random.randint() or random.choice(). A motivated attacker can figure out the internal state of the random module and predict your "random" tokens with frightening accuracy.
The secrets module was introduced in Python 3.6 specifically to solve this. It's a thin wrapper around os.urandom(), designed to be easy to use without accidentally compromising your security.
Generating unguessable tokens
Most of the time, you aren't looking for a raw number; you're looking for a string that a user can click in an email or a browser can store in a cookie. The secrets module gives us token_hex and token_urlsafe for exactly this purpose.
import secrets
# Perfect for a session ID or an API key
session_id = secrets.token_hex(16)
print(f"Session ID: {session_id}")
# Perfect for a password reset link (URL-safe characters)
reset_token = secrets.token_urlsafe(32)
print(f"Reset Token: {reset_token}")
Notice how token_urlsafe doesn't include characters that would break a URL (like + or /). It's a small detail, but it saves you from having to manually percent-encode your strings later.
The danger of the timing attack
Here is a pro tip that separates the juniors from the seniors. When you're checking if a token provided by a user matches the one in your database, don't use ==. Why? Because == stops comparing as soon as it finds a character that doesn't match. This means a token that's "almost right" takes slightly longer to fail than one that's "completely wrong." An attacker can use a high-resolution timer to guess your token character by character based on these tiny time differences.
To stop this, use secrets.compare_digest(). It takes the same amount of time regardless of how much of the string matches, effectively blinding the attacker.
import secrets
stored_token = "a7b8c9d0e1f2g3h4"
user_provided_token = "a7b8c9x0e1f2g3h4"
# DO NOT DO THIS:
# if user_provided_token == stored_token:
# print("Access granted")
# DO THIS INSTEAD:
if secrets.compare_digest(user_provided_token, stored_token):
print("Access granted")
else:
print("Access denied")
📋 Practical Task
Building a Secure Password Reset Token Generator
You need to implement a secure token generation and verification system for a user authentication module. Create a script that performs the following steps:
- Generate a cryptographically secure, URL-safe reset token with at least 32 bytes of entropy.
- Store this token in a variable (simulating a database save).
- Create a function called
verify_token(provided_token, actual_token)that uses a constant-time comparison to check if the provided token matches the actual token. - Test your function with both a correct token and an incorrect token, printing the result of the verification.
There are no comments for now.