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 this

Generating 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 number

API 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_id

Constant-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: