The secrets module generates cryptographically secure random values. For passwords, tokens, and anything security-sensitive, always use secrets—never random.
Why Not random?
The random module is predictable:
import random
# DON'T do this for security!
random.seed(42)
token = ''.join(random.choices('abcdef0123456789', k=32))
# Anyone who knows the seed can predict the outputThe secrets module uses the OS's secure random source:
import secrets
# DO this for security
token = secrets.token_hex(16) # Cryptographically secureSecure Token Generation
import secrets
# Hex string (32 hex chars = 16 bytes = 128 bits)
hex_token = secrets.token_hex(16)
print(hex_token) # e.g., '9a4f2c8e1b3d5f7a0c2e4d6b8a1f3c5e'
# URL-safe base64 (shorter for same entropy)
url_token = secrets.token_urlsafe(16)
print(url_token) # e.g., 'dGhpcyBpcyBhIHRva2Vu'
# Raw bytes
raw_token = secrets.token_bytes(16)
print(raw_token) # b'\x9a\x4f\x2c...'Password Generation
import secrets
import string
def generate_password(length=16):
"""Generate a secure random password."""
alphabet = string.ascii_letters + string.digits + string.punctuation
return ''.join(secrets.choice(alphabet) for _ in range(length))
print(generate_password()) # e.g., 'K#9mP$xL2!qR@nTz'Password with Requirements
import secrets
import string
def generate_strong_password(length=16):
"""Generate password with guaranteed character types."""
# Ensure at least one of each type
password = [
secrets.choice(string.ascii_lowercase),
secrets.choice(string.ascii_uppercase),
secrets.choice(string.digits),
secrets.choice(string.punctuation),
]
# Fill the rest randomly
alphabet = string.ascii_letters + string.digits + string.punctuation
password += [secrets.choice(alphabet) for _ in range(length - 4)]
# Shuffle to avoid predictable positions
secrets.SystemRandom().shuffle(password)
return ''.join(password)API Keys
import secrets
def generate_api_key():
"""Generate a 256-bit API key."""
return secrets.token_urlsafe(32) # 43 characters
api_key = generate_api_key()
print(f"API-{api_key}")Session Tokens
import secrets
def generate_session_id():
"""Generate a secure session identifier."""
return secrets.token_hex(32) # 256 bits
session_id = generate_session_id()Secure Random Choice
import secrets
# Choose from a sequence
colors = ['red', 'green', 'blue', 'yellow']
chosen = secrets.choice(colors)
# Random integer in range
number = secrets.randbelow(100) # 0 to 99
# Random bits
bits = secrets.randbits(128) # 128-bit random integerConstant-Time Comparison
Prevent timing attacks when comparing secrets:
import secrets
def verify_token(user_token: str, stored_token: str) -> bool:
"""Securely compare tokens without timing leaks."""
return secrets.compare_digest(user_token, stored_token)
# DON'T do this - vulnerable to timing attacks
# return user_token == stored_tokenPassword Reset Tokens
import secrets
from datetime import datetime, timedelta
def create_reset_token():
"""Create a password reset token with expiry."""
return {
'token': secrets.token_urlsafe(32),
'expires': datetime.utcnow() + timedelta(hours=1)
}
def verify_reset_token(user_token, stored_token, expiry):
"""Verify reset token securely."""
if datetime.utcnow() > expiry:
return False
return secrets.compare_digest(user_token, stored_token)One-Time Passwords
import secrets
def generate_otp(length=6):
"""Generate a numeric OTP."""
return ''.join(str(secrets.randbelow(10)) for _ in range(length))
otp = generate_otp()
print(f"Your code is: {otp}") # e.g., "847293"Secure Filename Generation
import secrets
def secure_filename(extension='.tmp'):
"""Generate a secure random filename."""
return secrets.token_hex(16) + extension
filename = secure_filename('.pdf')
# e.g., '9a4f2c8e1b3d5f7a0c2e4d6b8a1f3c5e.pdf'How Many Bytes?
| Bytes | Bits | Hex Chars | Use Case |
|---|---|---|---|
| 16 | 128 | 32 | Session IDs, tokens |
| 24 | 192 | 48 | API keys |
| 32 | 256 | 64 | High-security tokens |
| 64 | 512 | 128 | Extreme security |
General rule: 16-32 bytes is sufficient for most applications.
SystemRandom Class
For compatibility with random module interface:
import secrets
# Use like random module, but secure
rng = secrets.SystemRandom()
rng.shuffle(my_list)
rng.sample(population, k=5)
rng.randint(1, 100)Best Practices
- Always use secrets for security - never random
- Use compare_digest for secret comparison
- Use enough entropy - minimum 128 bits (16 bytes)
- Token expiration - always expire sensitive tokens
- Don't log secrets - ever
secrets vs random vs os.urandom
| Module | Use For |
|---|---|
secrets | Security-sensitive random values |
random | Simulations, games, non-security uses |
os.urandom | Raw bytes (secrets uses this internally) |
The secrets module is the right choice for anything security-related. It's simple, secure, and does exactly what you need.
React to this post: