When security matters, don't use random. The secrets module provides cryptographically strong random numbers suitable for passwords, tokens, and security keys.
Why Not random?
import random
# DON'T use for security - predictable!
token = ''.join(random.choices('abcdef0123456789', k=32))
# random is seeded and can be predicted
random.seed(42) # Attacker could guess thisGenerating Secure Tokens
import secrets
# Hex token (32 bytes = 64 hex chars)
token = secrets.token_hex(32)
# 'a3f2b1c9e8d7...' (64 chars)
# URL-safe base64 token
api_key = secrets.token_urlsafe(32)
# 'dGhpcyBpcyBhIHNlY3VyZSB0b2tlbg...'
# Raw bytes
raw = secrets.token_bytes(32)
# b'\xa3\xf2\xb1\xc9...'Secure Password Generation
import secrets
import string
def generate_password(length=16):
alphabet = string.ascii_letters + string.digits + string.punctuation
# 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
password += [secrets.choice(alphabet) for _ in range(length - 4)]
# Shuffle to randomize position
secrets.SystemRandom().shuffle(password)
return ''.join(password)
print(generate_password()) # 'K9@mP2nL#xQw5vRt'Secure Random Selection
import secrets
# Choose from a sequence
winner = secrets.choice(['Alice', 'Bob', 'Charlie'])
# Generate random integer in range
dice_roll = secrets.randbelow(6) + 1 # 1-6
# Random bits
random_bits = secrets.randbits(256) # 256-bit numberAPI Key Generation
import secrets
from datetime import datetime
def generate_api_key(prefix='sk'):
"""Generate API key like sk_live_abc123..."""
token = secrets.token_urlsafe(32)
return f"{prefix}_{token}"
def generate_api_key_pair():
"""Generate public/secret key pair."""
return {
'public_key': generate_api_key('pk'),
'secret_key': generate_api_key('sk'),
}
keys = generate_api_key_pair()
# {'public_key': 'pk_abc...', 'secret_key': 'sk_xyz...'}Session Token Generation
import secrets
import hashlib
import time
def generate_session_token(user_id: int) -> str:
"""Generate secure session token."""
random_part = secrets.token_bytes(32)
timestamp = str(time.time()).encode()
user_bytes = str(user_id).encode()
# Combine and hash
combined = random_part + timestamp + user_bytes
return hashlib.sha256(combined).hexdigest()
token = generate_session_token(12345)Password Reset Tokens
import secrets
from datetime import datetime, timedelta
class TokenManager:
def __init__(self):
self.tokens = {}
def create_reset_token(self, user_id: int) -> str:
token = secrets.token_urlsafe(32)
self.tokens[token] = {
'user_id': user_id,
'expires': datetime.now() + timedelta(hours=1),
}
return token
def validate_token(self, token: str) -> int | None:
if token not in self.tokens:
return None
data = self.tokens[token]
if datetime.now() > data['expires']:
del self.tokens[token]
return None
return data['user_id']
def consume_token(self, token: str) -> int | None:
user_id = self.validate_token(token)
if user_id:
del self.tokens[token]
return user_idConstant-Time Comparison
Prevent timing attacks when comparing secrets:
import secrets
stored_token = "secret_token_abc123"
user_token = "secret_token_abc123"
# DON'T: vulnerable to timing attack
if stored_token == user_token: # Bad!
pass
# DO: constant-time comparison
if secrets.compare_digest(stored_token, user_token):
print("Token valid")One-Time Passwords (OTP)
import secrets
def generate_otp(length=6) -> str:
"""Generate numeric OTP."""
return ''.join(str(secrets.randbelow(10)) for _ in range(length))
def generate_alphanumeric_otp(length=8) -> str:
"""Generate alphanumeric OTP (no confusing chars)."""
# Exclude 0, O, l, 1, I for readability
alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
return ''.join(secrets.choice(alphabet) for _ in range(length))
print(generate_otp()) # '847293'
print(generate_alphanumeric_otp()) # 'KM7P2XNQ'Secure Nonce Generation
import secrets
import base64
def generate_nonce() -> str:
"""Generate nonce for CSRF protection or cryptographic operations."""
return base64.b64encode(secrets.token_bytes(32)).decode('ascii')
def generate_csrf_token() -> str:
"""Generate CSRF token for forms."""
return secrets.token_hex(32)Invitation Codes
import secrets
def generate_invite_code(length=8) -> str:
"""Generate human-readable invitation code."""
# Use uppercase and digits, no confusing characters
chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
return ''.join(secrets.choice(chars) for _ in range(length))
# Group for readability
def generate_formatted_code() -> str:
code = generate_invite_code(12)
return f"{code[:4]}-{code[4:8]}-{code[8:]}"
print(generate_formatted_code()) # 'XKCD-M9PL-2QRW'SystemRandom for Full Random API
Access the full random module API with cryptographic security:
import secrets
# SystemRandom has same interface as random module
rng = secrets.SystemRandom()
# All standard random operations, but secure
rng.shuffle(my_list)
rng.sample(population, k=5)
rng.uniform(0.0, 1.0)
rng.gauss(mu=0, sigma=1)Best Practices
import secrets
# 1. Use appropriate token length
# 16 bytes minimum for security-critical tokens
api_token = secrets.token_urlsafe(32) # 256 bits
# 2. Always use compare_digest for comparisons
if secrets.compare_digest(expected, actual):
authenticate()
# 3. Don't reuse tokens
# Generate fresh token for each operation
# 4. Set expiration
# Tokens should have limited lifetime
# 5. Hash stored tokens
import hashlib
stored_hash = hashlib.sha256(token.encode()).hexdigest()The secrets module is your go-to for anything security-related. If you're generating tokens, passwords, or cryptographic values, always reach for secrets over random.
React to this post: