Secure Random Numbers
Python has two random number families, and mixing them up is a classic security bug: random is a fast Mersenne Twister for simulation and games, while secrets (and os.urandom) is a cryptographically secure source for anything an attacker could observe. The MT19937 state can be reconstructed from about 624 consecutive outputs, so a session token built on random is predictable - a real vulnerability, not a theoretical one.
The secrets Module
import secrets
secrets.token_bytes(32) # 32 raw bytes - keys, IVs, salts
secrets.token_hex(16) # 32-character hex string
secrets.token_urlsafe(24) # URL-safe base64 - session tokens, reset links
secrets.choice([1, 2, 3, 4]) # uniform pick, no modulo bias
secrets.randbelow(100) # uniform int in [0, 100)
Everything here draws from the OS entropy source (getrandom, /dev/urandom on Unix). There is no seeding: no seed(), no reproducibility - which is exactly what you want from security values.
os.urandom
import os
iv = os.urandom(16) # AES-CBC IV
nonce = os.urandom(12) # GCM nonce
salt = os.urandom(16) # KDF salt
os.urandom(n) is the raw primitive that secrets wraps; use it when you want bytes for a specific construction. secrets.SystemRandom() exposes the same source as a random.Random-compatible object.
What Is Not for Security
| Source | Use for | Not for |
|---|---|---|
random.random() | simulation, shuffling test data, games | tokens, keys, IVs |
| time-based seeds | never for security | anything guessable |
uuid.uuid4() | identifiers (wraps os.urandom) | keys - treat it as an ID |
secrets.* | keys, nonces, tokens, passwords | deterministic tests (seed those elsewhere) |
Constant-Time Comparison
Generating secure values is half the job; comparing them without leaking timing is the other half:
import secrets
provided = "k9xQ..." # e.g. from a cookie or header
expected = "k9xQ..." # the token you issued
secrets.compare_digest(provided.encode(), expected.encode()) # True/False
compare_digest runs in time independent of where the first mismatch is, closing the timing channel that a plain == leaves open on tokens and MACs.
Summary
The rule of thumb: if an attacker could profit from guessing the value, it comes from secrets or os.urandom - tokens, nonces, IVs, salts, keys, password resets. Keep random for math and games, and compare secrets with compare_digest.