The secrets module generates cryptographically secure random numbers. Use it for tokens, passwords, and security-sensitive randomness.
Why Not random?
import random
import secrets
# random is NOT secure - predictable seed
random.seed(42)
print(random.randint(0, 100)) # Always 81
# secrets uses OS entropy - unpredictable
print(secrets.randbelow(100)) # Different each time
# Never use random for:
# - Passwords
# - Tokens
# - Session IDs
# - API keys
# - Anything security-relatedBasic Functions
import secrets
# Random integer below n
secrets.randbelow(100) # 0 to 99
# Random bits
secrets.randbits(128) # 128-bit random integer
# Random bytes
secrets.token_bytes(32) # 32 random bytes
# Random hex string
secrets.token_hex(32) # 64-character hex string
# URL-safe token
secrets.token_urlsafe(32) # ~43 characters, base64Generating Tokens
import secrets
# Session tokens
session_id = secrets.token_hex(32) # 64 hex chars
# e.g., "a1b2c3d4e5f6..."
# URL-safe tokens (for password reset links, etc.)
reset_token = secrets.token_urlsafe(32)
# e.g., "Drmhze6EPcv0fN_81Bj..."
# API keys
api_key = secrets.token_urlsafe(32)
# Shorter tokens (when collision risk is acceptable)
short_token = secrets.token_hex(8) # 16 hex charsPassword 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))
def generate_passphrase(words=4, wordlist=None):
"""Generate a passphrase from random words."""
if wordlist is None:
# Simple word list (use a real one in production)
wordlist = ["apple", "banana", "cherry", "dragon", "eagle",
"falcon", "grape", "hammer", "island", "jungle"]
return "-".join(secrets.choice(wordlist) for _ in range(words))
# Usage
print(generate_password()) # "Kx#9mP!qR2$vL@nT"
print(generate_passphrase()) # "falcon-grape-island-dragon"Password Requirements
import secrets
import string
def generate_strong_password(length=16):
"""Generate password meeting common requirements."""
# Ensure at least one of each required 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)Comparing Secrets Securely
import secrets
# DON'T use == for secret comparison (timing attack vulnerable)
# if user_token == stored_token: # BAD
# DO use constant-time comparison
if secrets.compare_digest(user_token, stored_token):
print("Token valid")
# Works with strings or bytes
secrets.compare_digest(b"secret1", b"secret1") # True
secrets.compare_digest("secret1", "secret2") # FalseChoosing Random Items
import secrets
# Random choice from sequence
colors = ["red", "green", "blue", "yellow"]
chosen = secrets.choice(colors)
# Random sample (without replacement)
# Use secrets.SystemRandom() for this
rng = secrets.SystemRandom()
sample = rng.sample(colors, k=2)
# Shuffle securely
items = [1, 2, 3, 4, 5]
rng.shuffle(items)SystemRandom
import secrets
# Full random.Random interface with secure randomness
rng = secrets.SystemRandom()
# All random module methods, but secure
rng.random() # Float [0.0, 1.0)
rng.randint(1, 100) # Integer [1, 100]
rng.uniform(0, 10) # Float [0, 10]
rng.choice([1, 2, 3]) # Random element
rng.shuffle([1, 2, 3]) # In-place shuffle
rng.sample([1,2,3], 2) # Random sampleCommon Patterns
import secrets
def generate_otp(length=6):
"""Generate one-time password (numeric)."""
return "".join(str(secrets.randbelow(10)) for _ in range(length))
def generate_confirmation_code():
"""Generate human-readable confirmation code."""
# Uppercase letters + digits, excluding confusing chars
alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
return "".join(secrets.choice(alphabet) for _ in range(8))
def generate_api_key():
"""Generate API key with prefix."""
return f"sk_live_{secrets.token_urlsafe(32)}"
def generate_invite_code():
"""Generate short invite code."""
return secrets.token_urlsafe(8)[:12]Token Length Guidelines
import secrets
# Minimum recommended lengths:
# Session IDs: 128 bits (32 hex chars)
session = secrets.token_hex(16)
# CSRF tokens: 128 bits
csrf = secrets.token_hex(16)
# Password reset tokens: 256 bits (for longer validity)
reset = secrets.token_urlsafe(32)
# API keys: 256 bits
api_key = secrets.token_urlsafe(32)
# Short-lived OTP: 6-8 digits is common
otp = generate_otp(6)Entropy Calculation
import secrets
import math
def entropy_bits(length, alphabet_size):
"""Calculate entropy in bits."""
return length * math.log2(alphabet_size)
# Examples
print(entropy_bits(16, 62)) # alphanumeric: ~95 bits
print(entropy_bits(16, 94)) # ASCII printable: ~105 bits
print(entropy_bits(32, 16)) # hex: 128 bits
print(entropy_bits(32, 64)) # base64: 192 bits
# Recommended: at least 128 bits for security tokensFile-Based Secrets
import secrets
from pathlib import Path
def get_or_create_secret(path: str, length: int = 32) -> str:
"""Get existing secret or create new one."""
secret_path = Path(path)
if secret_path.exists():
return secret_path.read_text().strip()
secret = secrets.token_urlsafe(length)
secret_path.write_text(secret)
secret_path.chmod(0o600) # Owner read/write only
return secret
# Usage
app_secret = get_or_create_secret(".app_secret")Best Practices
import secrets
# Always use secrets for security-sensitive randomness
token = secrets.token_urlsafe(32) # Good
# Never use random for security
# token = ''.join(random.choices(...)) # Bad
# Use sufficient length (128+ bits)
secrets.token_bytes(16) # Minimum 16 bytes
# Use constant-time comparison
secrets.compare_digest(a, b)
# Don't log or expose secrets
# print(f"Token: {token}") # Bad for production
# Rotate secrets periodically
# Store securely (env vars, secret managers)secrets vs os.urandom
import secrets
import os
# Both use system entropy
secrets.token_bytes(32) # Higher-level API
os.urandom(32) # Lower-level, same source
# secrets provides convenience functions
secrets.token_hex(32) # No equivalent in os
secrets.token_urlsafe(32) # No equivalent in os
secrets.choice(items) # No equivalent in os
secrets.compare_digest() # Available in hmac too
# Use secrets unless you need raw bytesThe secrets module is your go-to for security-sensitive randomness. Never use random for passwords, tokens, or cryptographic purposes.
React to this post: