Python Secrets Token Generation — Core Concepts
Why a Dedicated Module Exists
Python 3.6 introduced secrets specifically because developers kept using random for security-sensitive tasks. The random module uses a Mersenne Twister PRNG — fast and statistically uniform, but deterministic. If an attacker observes 624 consecutive outputs, they can reconstruct the internal state and predict every future value. For simulations that’s irrelevant; for authentication tokens it’s catastrophic.
The secrets module wraps os.urandom(), which pulls from the operating system’s cryptographic random source (/dev/urandom on Linux, CryptGenRandom on Windows). These sources harvest entropy from hardware interrupts, disk timing, and other unpredictable physical events.
The Core Functions
secrets.token_bytes(n) — Returns n random bytes as a bytes object. Useful as raw key material or when you need to feed bytes into another cryptographic function.
secrets.token_hex(n) — Returns 2n hex characters. Common for database-stored tokens where you want a printable, fixed-length string.
secrets.token_urlsafe(n) — Returns a base64url-encoded string (roughly 1.3 × n characters). This is the go-to for anything that travels in URLs: password reset links, email verification tokens, OAuth state parameters.
secrets.choice(sequence) — Picks a single item from a sequence using a cryptographically secure source. Handy for generating passphrases from word lists or selecting random characters for a password.
secrets.randbelow(n) — Returns a random integer in [0, n). The secure equivalent of random.randrange(n).
How Much Entropy Is Enough?
The n parameter controls byte count, not string length. A 32-byte token has 256 bits of entropy — more than enough to resist brute-force attacks even with nation-state resources. The Python documentation recommends at least 32 bytes for tokens that must resist online and offline attacks.
For context: a 16-byte (128-bit) token would require an average of 2¹²⁷ guesses to crack. At a trillion guesses per second, that’s roughly 5 × 10¹⁸ years.
Common Patterns in Production
Password reset links — Generate a token_urlsafe(32), store its SHA-256 hash in the database alongside the user ID and an expiration timestamp. When the user clicks the link, hash the incoming token and compare. This way, a database leak doesn’t expose valid tokens.
API keys — Use token_hex(32) to create a 64-character key. Prefix it with a service identifier (e.g., sk_live_) so developers can identify which service a leaked key belongs to.
One-time codes (OTP) — For 6-digit numeric codes: secrets.randbelow(1_000_000) padded with str.zfill(6). Set a short TTL (5–10 minutes) and limit verification attempts to prevent brute-force.
Passphrase generation — Load a word list and call secrets.choice(words) repeatedly. Four words from a 7,776-word Diceware list yield about 51 bits of entropy — adequate for human-memorable passwords with rate limiting.
Common Misconception
Many developers believe random.SystemRandom() and secrets are interchangeable. They do share the same entropy source, but secrets is purpose-built with convenience functions for token generation and comparison. More importantly, using secrets signals intent: anyone reading the code understands this value must be unpredictable. Using random.SystemRandom leaves ambiguity about whether the developer considered security.
Timing-Safe Comparison
The module provides secrets.compare_digest(a, b), which compares two strings or bytes in constant time. Regular == comparison short-circuits on the first mismatched character, leaking information about how many characters matched. An attacker can exploit this timing difference to reconstruct a token character by character. Always use compare_digest when verifying tokens against stored values.
The one thing to remember: secrets is small by design — a handful of functions that do one job right. The hard part isn’t learning the API; it’s remembering to use it every time a value must stay unpredictable.
See Also
- Python Certificate Pinning Why your Python app should remember which ID card a server uses — and refuse impostors even if they have official-looking badges.
- Python Cryptography Library Understand Python Cryptography Library with a vivid mental model so secure Python choices feel obvious, not scary.
- Python Dependency Vulnerability Scanning Why the libraries your Python project uses might be secretly broken — and how to find out before hackers do.
- Python Hashlib Hashing How Python turns any data into a unique fingerprint — and why that fingerprint can never be reversed.
- Python Hmac Authentication How Python proves a message wasn't tampered with — using a secret handshake only you and the receiver know.