Generating Secrets with the secrets Module
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()