Have you ever thought about how your computer decides which number comes next in a lottery? In programming, creating randomness is not as simple as it seems. If you are building a security system such as a Python password generator, using the wrong tool can leave the door open for hackers. This is where the secrets module comes in — the definitive solution for anyone who needs real cryptographic security. This article shows how to generate secure random numbers in Python with secrets and explains why it is superior to traditional methods.
Two types of randomness
In computing there are two main types of random numbers: pseudo-random and cryptographically secure. Most functions used day-to-day, like those in the Python random module, belong to the first group. They are excellent for simulations and games but have a critical weakness: they are based on deterministic algorithms. If someone discovers the initial value (called the seed), they can predict every subsequent number.
For protecting sensitive data you need the second type. According to Wikipedia, modern cryptography depends entirely on entropy sources that cannot be reproduced by attackers.
random vs secrets: key differences
Python’s own documentation warns that the random module should not be used for security or cryptography purposes — it was designed for statistical modeling and speed, not protection against attacks. The secrets module, introduced in Python 3.6, fills that gap. It uses randomness sources provided by the operating system (such as /dev/urandom on Unix or CryptGenRandom on Windows), which draw from hardware noise and unpredictable system events.
Generating secure integers
import secrets
# Secure random integer between 0 and 99
secure_number = secrets.randbelow(100)
print(f"Your secure number: {secure_number}")
# Random number with 16 bits of depth
secure_bits = secrets.randbits(16)
print(f"16-bit number: {secure_bits}")Creating tokens for authentication and URLs
Often you need secure text sequences rather than numbers. The secrets module makes creating hexadecimal tokens or Base64-encoded strings straightforward — essential for password reset links or API keys.
import secrets
# Secure hex token (32 characters)
hex_token = secrets.token_hex(16)
print(f"Hex Token: {hex_token}")
# URL-safe token (Base64)
url_token = secrets.token_urlsafe(16)
print(f"URL Token: {url_token}")Using token_urlsafe is strongly recommended because it generates characters that do not need manual encoding, avoiding errors such as UnicodeDecodeError.
Choosing elements securely
secrets.choice() works identically to the random version but with the security engine behind it. Use it whenever you need to pick from a list without predictable patterns.
Building a robust password generator
import string
import secrets
def generate_secure_password(length=16):
"""Generate a strong password using the secrets module."""
characters = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(characters) for _ in range(length))
return password
if __name__ == "__main__":
new_password = generate_secure_password(20)
print("Your new secure password:", new_password)
# Generating a token for account recovery
recovery_token = secrets.token_urlsafe(32)
print("Security token:", recovery_token)Best practices for sensitive data
Never display secrets in logs — avoid using the logging system to record tokens generated by secrets. For comparing a user-supplied token against a stored one, use secrets.compare_digest(a, b). This prevents timing attacks where hackers deduce the correct value by measuring server response times. Use at least 32 bytes of entropy for security tokens to ensure brute-force protection. Consulting OWASP recommendations is always a good idea for anyone wanting to go deeper into web application security.
Comparison: secrets vs random
The random module has no cryptographic security and is deterministic with a known seed, but is fast and ideal for simulations and games. The secrets module is cryptographically secure and non-deterministic, making it the right choice for passwords, tokens, and authentication keys — at a slightly higher computational cost that is imperceptible in most applications.
Frequently Asked Questions
Is secrets available in all Python versions?
It was introduced in Python 3.6. Earlier versions require updating the development environment.
Can I use secrets to create random file names?
Yes. Using secrets.token_hex() for upload file names is a great practice that prevents collisions and file enumeration attacks.
Why use secrets.compare_digest instead of ==?
This function compares two strings in constant time regardless of how similar they are, preventing hackers from deducing the secret by measuring server response time.
Does secrets completely replace random?
No. For statistical simulations, scientific experiments, or games, random is still preferable because it is faster. Use secrets only where unpredictability is a security requirement.
Can I use secrets to generate AES encryption keys?
Yes. secrets.token_bytes(32) is the ideal way to generate a 256-bit key for symmetric encryption algorithms.
How many characters should a secure password have?
Current recommendations suggest a minimum of 12 to 16 characters. Using secrets guarantees those characters are truly random.






