Kryptografická bezpečnost generování hesel v Pythonu: random vs secrets
Entropie určuje kvalitu hesla nikoli jako vlastnost řetězce, ale jako charakteristiku procesu generování. Vzorec H = L × log₂(N) předpokládá nezávislý a rovnoměrný výběr každého znaku z abecedy o velikosti N na délce L. Pro 8 malých písmen (N=26): ~37,6 bitů. Pro plný ASCII (N=94): ~79 bitů.
Omyl: vnější náhodnost hesla nezaručuje odolnost. Dvě hesla jako f9A$kL2pQzX1 mohou mít stejnou formální entropii, ale rozdílnou reálnou bezpečnost v závislosti na generátoru.
Modul random: deterministický PRNG
Modul random implementuje Mersenne Twister (MT19937) s vnitřním stavem ~19937 bitů. Každá hodnota je determinována předchozí. Seed (počáteční hodnota) se standardně bere z os.urandom(), ale vlastnosti PRNG zůstávají:
- Determinismus: jeden seed — jedna sekvence.
- Obnovitelnost: stav lze rekonstruovat z ~624 surových 32bitových výstupů.
- Absence kryptografické odolnosti: není odolný vůči útokům na stav.
Příklad typického kódu:
import random
import string
alphabet = string.ascii_letters + string.digits
def generate_password(length=12):
return ''.join(random.choice(alphabet) for _ in range(length))
Heslo je výřez sekvence PRNG. Pokud je stav znám, sekvence je předvídatelná.
Modul secrets: přímý přístup k systémové entropii
Secrets používá os.urandom() přímo pro každé volání:
- secrets.randbelow(n) → os.urandom().
- random.SystemRandom() → os.urandom().
Žádný pevný stav, žádný determinismus. Každý bajt je čerpán z hardwarových zdrojů: přerušení, síťový šum, pohyby.
Rozdíl je zásadní: random vytváří iluzi odolnosti, secrets ji zajišťuje.
Praktický útok: obnovení seed a stavu
Experiment s 5 po sobě jdoucími hesly od GigaChat (délka 12, Python 3.9.18, čas 24.03.2026 11:11):
Hypotéza: seed = časové razítko serveru ±1 hodina.
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())
# Inicializace random.seed(seed) a kontrola
Výsledek: žádné shody. os.urandom() pro seed znemožňuje brute-force podle času.
Reálný útok na MT19937 vyžaduje 624 surových hodnot. Hesla — zkreslený výstup (choice → znak), celkem 60 výběrů za 5 hesel. Obnovení není možné.
Hranice útoku:
- Vyžaduje surové výstupy PRNG.
- Předvídatelný seed (čas, PID).
- Opakované použití jednoho generátoru.
- Známý algoritmus.
Co je důležité
- Entropie — vlastnost procesu, ne výsledku: kontrolujte generátor.
- random (Mersenne Twister) je zranitelný vůči obnovení stavu při dostatečných datech.
- secrets/os.urandom — jediná volba pro hesla, klíče, tokeny.
- Iluze náhodnosti od random je nebezpečná v produkci.
- Útoky na PRNG jsou reálné pouze při chybách v seed nebo úniku stavů.
Doporučení pro middle/senior vývojáře
Implementujte do kódu:
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))
Vyhýbejte se:
- random pro autentizaci.
- Pevným seed.
- Znovupoužívání generátoru.
Testujte: generujte 10⁶ hesel, kontrolujte rovnoměrné rozdělení (chi²).
— Editorial Team
Zatím žádné komentáře.