Generating Secrets with the secrets Module

· 4 min read · Updated March 14, 2026 · beginner
python security stdlib random tokens

When you need random values that actually matter—API keys, session tokens, password reset codes—you cannot rely on random.random(). That module is designed for simulations and games, not security. Python’s secrets module is built specifically for generating cryptographically strong random values suitable for passwords, authentication tokens, and security-critical operations.

Why Use the secrets Module?

The random module uses a pseudo-random number generator (PRNG). Given enough output, an attacker can predict future values. The secrets module uses the operating system’s source of randomness, making predictions practically impossible.

Use secrets when generating:

  • Passwords and passphrases
  • Session tokens and API keys
  • Confirmation codes
  • Cryptographic keys
  • CSRF tokens for web applications

Generating Random Numbers

The secrets module provides functions for generating random integers:

import secrets

# Generate a random integer between 0 and 255
random_byte = secrets.randbelow(256)
print(random_byte)  # Example: 143

# Generate a random integer in a range
random_number = secrets.randrange(1, 100)  # 1 to 99
print(random_number)  # Example: 42

For more control over the bit size:

import secrets

# Generate a 32-bit random number
token_32 = secrets.getrandbits(32)
print(token_32)  # Example: 2147483647

# Generate a 64-bit random number
token_64 = secrets.getrandbits(64)
print(token_64)  # Example: 9223372036854775807

Generating Tokens and Strings

The most common use case is generating random tokens for authentication or API keys:

import secrets

# Generate a random bytes object
token_bytes = secrets.token_bytes(32)
print(token_bytes)  # b'\\x8d\\x969...'

# Generate a hexadecimal string
token_hex = secrets.token_hex(32)
print(token_hex)  # 64 character hex string

# Generate a URL-safe base64 string
token_urlsafe = secrets.token_urlsafe(32)
print(token_urlsafe)  # Example: wGzMC...

The argument specifies the number of bytes. secrets.token_hex(32) produces a 64-character hex string (32 bytes = 64 hex chars).

Comparing Token Types

Each token format has different use cases:

import secrets

# Hex: Easy to read and debug
hex_token = secrets.token_hex(16)
print(f"Hex: {hex_token}")  # 32 chars, lowercase a-f, 0-9

# URL-safe base64: Compact, safe for URLs
url_token = secrets.token_urlsafe(16)
print(f"URL-safe: {url_token}")  # ~22 chars, URL-safe

# Bytes: Raw binary data
raw_token = secrets.token_bytes(32)
print(f"Bytes: {raw_token}")  # 32 bytes of raw data

Choose based on your storage and transmission requirements. Hex is easiest to log and debug. Base64 is more compact. Raw bytes are best when you need exact byte values.

Generating Passwords

You can build password generators using secrets combined with string constants:

import secrets
import string

def generate_password(length=16):
    """Generate a random password with letters, digits, and punctuation."""
    alphabet = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(secrets.choice(alphabet) for _ in range(length))
    return password

# Generate a 16-character password
new_password = generate_password(16)
print(new_password)  # Example: aB3$kL9@mN5#pQ7!

For web applications, you might want to avoid ambiguous characters:

import secrets
import string

def generate_readable_password(length=12):
    """Generate password without ambiguous characters."""
    alphabet = string.ascii_letters + string.digits
    # Remove ambiguous: 0, O, l, 1, I
    alphabet = alphabet.replace('0', '').replace('O', '').replace('l', '').replace('1', '').replace('I', '')
    password = ''.join(secrets.choice(alphabet) for _ in range(length))
    return password

print(generate_readable_password(12))  # Example: aBcDeFgHiJkL

One-Time Tokens

For email verification or password reset, you need short numeric codes:

import secrets

def generate_verification_code(digits=6):
    """Generate a numeric verification code."""
    return str(secrets.randbelow(10 ** digits)).zfill(digits)

code = generate_verification_code(6)
print(code)  # Example: 482951

code = generate_verification_code(8)
print(code)  # Example: 73925184

The zfill() ensures the code always has the full length, including leading zeros.

Comparing Values Securely

When checking tokens or passwords, timing attacks can leak information. The secrets.compare_digest() function compares values in constant time:

import secrets

def verify_token(submitted_token, stored_token):
    """Compare tokens without timing leaks."""
    return secrets.compare_digest(submitted_token, stored_token)

# Example usage
user_submitted = "abc123"
actual_token = "abc123"

if verify_token(user_submitted, actual_token):
    print("Token verified!")
else:
    print("Invalid token")

This prevents attackers from measuring how long comparisons take to deduce the correct token character by character.

Practical Examples

API Key Generation

import secrets

def create_api_key():
    """Generate a secure API key."""
    prefix = "sk_live_"
    random_part = secrets.token_hex(32)
    return f"{prefix}{random_part}"

api_key = create_api_key()
print(api_key)  # sk_live_a1b2c3d4e5f6...

Session Identifier

import secrets

def create_session_id():
    """Generate a unique session identifier."""
    return secrets.token_urlsafe(32)

session = create_session_id()
print(session)  # Example: Zy9wqm...

Secret Key for HMAC

import secrets
import hmac
import hashlib

# Generate a signing key
signing_key = secrets.token_bytes(32)

def sign_message(message, key):
    """Sign a message using HMAC-SHA256."""
    return hmac.new(key, message.encode(), hashlib.sha256).hexdigest()

# Example usage
message = "Hello, World!"
signature = sign_message(message, signing_key)
print(f"Message: {message}")
print(f"Signature: {signature}")

See Also

  • random-module — The pseudo-random generator for non-security purposes
  • string-module — String constants for building alphabets
  • os-module — Operating system random sources via os.urandom()