Stop Using `random` for Sensitive Data: Use Python's `secrets` Module
This article argues why developers should replace Python's `random` module with `secrets` for passwords, tokens, and other security-critical tasks. It explains the cryptographic strength of `secrets`, provides code examples, and warns against common pitfalls.
Why You Should Stop Using random for Sensitive Data and Switch to Python’s secrets Module
Look, I’ll be straight with you: if you’re using Python’s random module to generate passwords, tokens, or anything security-related, you’re doing it wrong. I know, because I used to do it too. It’s not your fault—random is easy, intuitive, and works fine for games and simulations. But here’s the thing: random is predictable. Really predictable. And that’s a problem when you’re dealing with sensitive data.
The secrets module, introduced in Python 3.6, is built specifically for this purpose. It uses cryptographically strong random number generators, meaning the output is virtually impossible to predict—even if someone knows the algorithm. It’s designed for passwords, security tokens, and anything that needs to withstand attack.
What Makes secrets Different?
Under the hood, secrets uses your operating system’s secure random number generator (like /dev/urandom on Linux or CryptGenRandom on Windows). These sources are designed to be unpredictable, pulling from hardware entropy sources like mouse movements, keyboard timing, and other system noise.
In contrast, random uses a pseudo-random number generator (specifically Mersenne Twister) that’s deterministic. Give it the same seed, and you’ll get the same sequence of “random” numbers. That’s fine for Monte Carlo simulations—not for security tokens.
Getting Started with secrets
Let’s say you need to generate a random password for a user at PythonSkillset.com. Here’s the old way (don’t do this):
import random
import string
# DON'T DO THIS
password = ''.join(random.choices(string.ascii_letters + string.digits, k=12))
Now here’s the secure version using secrets:
import secrets
import string
# DO THIS INSTEAD
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
password = ''.join(secrets.choice(alphabet) for _ in range(16))
print(password) # Something like 'Gx$7#kLp@2mN9qR!'
The secrets.choice() function does exactly what random.choice() does—except securely. For a 16-character password using 72 possible characters, you get about 95 bits of entropy. That’s plenty strong.
Real-World Use Cases at PythonSkillset
1. Generating API Tokens
If you’re building an API that hands out authentication tokens, secrets is your friend:
import secrets
def generate_api_token():
return secrets.token_hex(32) # 64-character hex string
# Usage
new_token = generate_api_token()
# Result: 'a1b2c3d4e5f6...' (64 characters of hex)
The token_hex() method is perfect here. Each byte gets converted to two hex characters, so 32 bytes = 64 characters. That’s 256 bits of entropy—essentially unbreakable.
2. Creating Password Reset Links
When a user forgets their password, you need a unique, unpredictable URL:
import secrets
import hashlib
def generate_reset_token(user_id):
raw_token = secrets.token_urlsafe(32)
# Store hashed version in database for security
hashed_token = hashlib.sha256(raw_token.encode()).hexdigest()
return raw_token, hashed_token
# In practice:
token, hashed = generate_reset_token(12345)
# token = 'some-base64-url-safe-string'
# Store 'hashed' in DB, email 'token' to user
Notice I used token_urlsafe()—it generates base64-encoded strings that are URL-safe (no +, /, or = characters). Perfect for embedding in links.
3. Generating Session Keys
For web applications, session management is critical:
import secrets
def create_session_id():
return secrets.token_hex(16) # 32-character session ID
# Or even better, use token_bytes for raw entropy
session_key = secrets.token_bytes(16) # 16 random bytes
Common Pitfalls to Avoid
Mistake 1: Using secrets with random.seed()
# WRONG: Don't seed secrets
secrets.random.seed(42) # This doesn't work anyway
The secrets module doesn’t let you set a seed—and for good reason. If you could seed it, it would be predictable.
Mistake 2: Using secrets.choice() on small sequences
# WEAK: Only 10 possible values
token = ''.join(secrets.choice('0123456789') for _ in range(4))
# Only 10,000 possible combinations
Always use large enough character sets. For a 4-digit PIN, you’re limited to 10,000 combinations. For a 16-character password, use 72+ characters.
Mistake 3: Storing plaintext tokens
# WRONG: Don't store raw tokens
session['token'] = secrets.token_hex(16) # Never do this
# RIGHT: Store hashed version
hashed = hashlib.sha256(raw_token.encode()).hexdigest()
When to Use secrets vs random
| Use Case | Module | Reason |
|---|---|---|
| Password generation | secrets |
Predictability matters |
| Session tokens | secrets |
Must be unguessable |
| API keys | secrets |
Security critical |
| Monte Carlo simulation | random |
Speed over security |
| Game dice rolls | random |
Perfectly fine |
| Shuffling deck of cards | random |
No security implications |
A Quick Performance Note
Yes, secrets is slower than random. Generating 10,000 random passwords with random takes about 0.1 seconds; with secrets, maybe 0.3 seconds. But when generating sensitive data, you generate a handful at a time—the performance difference is negligible.
Wrapping It Up
At PythonSkillset.com, we’ve seen too many developers use random for security-critical tasks and realize their mistake only after a breach. The secrets module is simple, built-in, and purpose-built for this exact use case. It takes five minutes to replace random.choice() with secrets.choice() in your codebase—and that five minutes could save you a world of trouble.
Start using secrets today. Your users—and your security team—will thank you.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.