Back to Home

Python Password Generation: secrets instead of random

The article breaks down why the random module is unsuitable for password generation due to Mersenne Twister determinism. Comparison with secrets, entropy analysis and failed seed attack attempt. Recommendations for secure code.

Why random is dangerous for passwords in Python: secrets in action
Advertisement 728x90

Cryptographically Secure Password Generation in Python: random vs secrets

Entropy defines password quality not as a property of the string itself, but as a characteristic of the generation process. The formula H = L × log₂(N) assumes an independent and equally probable choice of each character from an alphabet of size N over a length L. For 8 lowercase letters (N=26): ~37.6 bits. For full ASCII (N=94): ~79 bits.

A common misconception: the apparent randomness of a password guarantees its strength. Two passwords like f9A$kL2pQzX1 can have the same formal entropy but vastly different real-world security depending on the generator used.

The random module: a deterministic PRNG

The random module implements the Mersenne Twister (MT19937) with an internal state of ~19937 bits. Each value is deterministic based on the previous one. The default seed is taken from os.urandom(), but the PRNG's inherent properties remain:

Google AdInline article slot
  • Determinism: one seed yields one predictable sequence.
  • Recoverability: the state can be reconstructed from ~624 raw 32-bit outputs.
  • Lack of cryptographic security: not resistant to state-recovery attacks.

Example of typical code:

import random
import string

alphabet = string.ascii_letters + string.digits
def generate_password(length=12):
    return ''.join(random.choice(alphabet) for _ in range(length))

The password is a slice of the PRNG sequence. If the state is known, the sequence is predictable.

The secrets module: direct access to system entropy

Secrets uses os.urandom() directly for each call:

Google AdInline article slot
  • secrets.randbelow(n) → os.urandom().
  • random.SystemRandom() → os.urandom().

There is no fixed state, no determinism. Each byte is drawn from hardware sources: interrupts, network noise, mouse movements.

The difference is critical: random creates an illusion of security, secrets provides it.

Practical attack: recovering seed and state

Experiment with 5 consecutive passwords from GigaChat (length 12, Python 3.9.18, time 24.03.2026 11:11):

Google AdInline article slot

Hypothesis: seed = server timestamp ±1 hour.

from datetime import datetime, timedelta

base_time = datetime(2026, 3, 24, 11, 11, 0)
window_seconds = 3600
for offset in range(-window_seconds, window_seconds):
    t = base_time + timedelta(seconds=offset)
    seed = int(t.timestamp())
    # Initialize random.seed(seed) and verify

Result: no matches. Using os.urandom() for the seed defeats brute-force time-based attacks.

A real attack on MT19937 requires 624 raw values. Passwords are a distorted output (choice → character), totaling only 60 choices for 5 passwords. Recovery is impossible.

Attack boundaries:

  • Requires raw PRNG outputs.
  • Predictable seed (time, PID).
  • Repeated use of the same generator.
  • Known algorithm.

Key takeaways

  • Entropy is a property of the process, not the result: always verify your generator.
  • random (Mersenne Twister) is vulnerable to state recovery given sufficient data.
  • secrets/os.urandom is the only choice for passwords, keys, and tokens.
  • The illusion of randomness from random is dangerous in production.
  • PRNG attacks are only realistic with seed errors or state leaks.

Recommendations for middle/senior developers

Implement in your code:

import secrets
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
def secure_password(length=16):
    return ''.join(secrets.choice(alphabet) for _ in range(length))

Avoid:

  • Using random for authentication.
  • Fixed seeds.
  • Reusing generators.

Test: generate 10⁶ passwords, check for uniform distribution (chi²).

— Editorial Team

Advertisement 728x90

Read Next