Secure randomness for tokens and secrets
Generate unpredictable tokens with the secrets module, and see why the random module is not for security.
On this page
Strings and imports.
- Generate unpredictable tokens with the secrets module, and see why the random module is not for security.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
Many security mechanisms depend on values an attacker cannot guess: session identifiers, password-reset tokens, API keys, and cryptographic keys. If the numbers are predictable, the protection is worthless, however strong the rest of the system is.
Python's random module uses the Mersenne Twister, which is a statistical generator designed for simulations. Its output is determined by its internal state, and given enough output an attacker can recover that state and predict later values. The first part of the example shows the extreme case: two generators with the same seed produce the same sequence. For anything security-related use the secrets module, which draws from the operating system's cryptographically secure random source.
secrets.token_urlsafe(32) returns 32 random bytes encoded with URL-safe base64, which is 43 characters. Thirty-two bytes is 256 bits of randomness, far beyond what can be guessed. Tokens should also be stored hashed when they are long-lived, compared in constant time, given an expiry, and used once when they grant a sensitive action such as resetting a password.
Code example
import random
import secrets
import string
first = random.Random(1234)
second = random.Random(1234)
print([first.randrange(10) for _ in range(5)] == [second.randrange(10) for _ in range(5)])
token = secrets.token_urlsafe(32)
alphabet = set(string.ascii_letters + string.digits + "-_")
print(len(token), set(token) <= alphabet)
print(secrets.token_urlsafe(32) != token)
print(secrets.compare_digest(token, token))True 43 True True True
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
import randomimport secretsimport string first = random.Random(1234)second = random.Random(1234)print([first.randrange(10) for _ in range(5)] == [second.randrange(10) for _ in range(5)]) token = secrets.token_urlsafe(32)alphabet = set(string.ascii_letters + string.digits + "-_")print(len(token), set(token) <= alphabet)print(secrets.token_urlsafe(32) != token)print(secrets.compare_digest(token, token))Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Two generators created with the same seed will follow exactly the same sequence. | — | — | |
| Identical sequences: random is deterministic, so anyone who learns the seed or state can predict it. | — | True | |
| 32 bytes of operating-system randomness, encoded for use in URLs. | — | True | |
| 32 bytes encode to 43 URL-safe characters, with no padding. | — | True 43 True | |
| A new call gives a new, unpredictable value. | — | True 43 True True | |
| Use compare_digest to compare secrets in constant time. | — | True 43 True True True |
Behind the scenes
How Python processes this code
Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.
Cost and common mistakes
Generating a token is O(n) in its length. It reads from the operating system's secure random generator.
- Never use random for passwords, tokens, or keys. It is predictable, even when it looks random.
- Seeding a security-sensitive generator with the time or a small number lets an attacker enumerate the possibilities.
- Secrets such as tokens should not be logged or placed in URLs that may be stored in server logs or browser history.
Try it yourself
How long is secrets.token_urlsafe(16)?
Reveal answer and reasoning
22 characters. 16 bytes is 128 bits and base64url without padding uses ceil(16 × 8 / 6) = 22 characters.