Here is an example of Python code that is vulnerable to predictable, insecurely generated tokens:
🥺 Vulnerable Code
import random
import string
import time
def create_password_reset_token(user):
# Vulnerable: a non-cryptographic PRNG, seeded from the clock
random.seed(int(time.time()))
token = "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(12))
user.reset_token = token # stored in clear, never expires
user.save()
return tokenrandom is a Mersenne Twister built for simulations, not secrets. Seeding it with the current second means an attacker who requests a reset at the same time can regenerate the exact token offline, and observing a few outputs is enough to recover the internal state and predict future ones. The token is also stored in plaintext and has no expiry, so a database read or an old email keeps working forever.
😎 Secure Code
Here is a version of the same code that is secured against predictable, insecurely generated tokens:
import hashlib
import secrets
from datetime import datetime, timedelta, timezone
def create_password_reset_token(user):
token = secrets.token_urlsafe(32) # 256 bits from the OS CSPRNG
user.reset_token_hash = hashlib.sha256(token.encode()).hexdigest()
user.reset_token_expires = datetime.now(timezone.utc) + timedelta(minutes=15)
user.save()
return token # emailed once, never stored in clear
def consume_password_reset_token(user, candidate):
if not user.reset_token_hash:
return False
digest = hashlib.sha256(candidate.encode()).hexdigest()
if not secrets.compare_digest(digest, user.reset_token_hash):
return False
if datetime.now(timezone.utc) > user.reset_token_expires:
return False
user.reset_token_hash = None # single use
user.save()
return Truesecrets pulls from the operating system CSPRNG, so 256 bits of entropy make guessing hopeless. Only a hash of the token is stored, which means a leaked database row cannot be replayed, the constant-time comparison avoids a timing oracle, and the fifteen minute window plus single-use invalidation shrinks the blast radius. Use the same approach for session ids, API keys, invite codes, and CSRF tokens.