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 output

The secrets module uses the OS's secure random source:

import secrets
 
# DO this for security
token = secrets.token_hex(16)  # Cryptographically secure

Secure 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 integer

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

Password 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?

BytesBitsHex CharsUse Case
1612832Session IDs, tokens
2419248API keys
3225664High-security tokens
64512128Extreme 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

  1. Always use secrets for security - never random
  2. Use compare_digest for secret comparison
  3. Use enough entropy - minimum 128 bits (16 bytes)
  4. Token expiration - always expire sensitive tokens
  5. Don't log secrets - ever

secrets vs random vs os.urandom

ModuleUse For
secretsSecurity-sensitive random values
randomSimulations, games, non-security uses
os.urandomRaw 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: