Aktuální situace: PHP2Shell. Dvě kritické zranitelnosti PHP (CVSS 9.2) umožňují spustit škodlivý kód bez přihlášení. Záplaty od 21. 10. 2026.Co nyní udělat →

Bezpečná náhodná čísla a tokeny v PHP pomocí random_bytes

Jak vývojáři v PHP vytvářejí tokeny pro odkazy k obnovení hesla, klíče API a jednorázové kódy: kryptograficky bezpečné zdroje, dostatečná délka, ukládání jako hash a omezená platnost.

Stav 5 min čteníBezpečný vývoj

Stručně: Bezpečné tokeny vytvoříte v PHP pomocí random_bytes(), například 32 bajtů, které převedete na text pomocí bin2hex() nebo Base64-URL. Pro náhodná čísla v rozsahu existuje random_int(), pro kódy z pevné sady znaků od PHP 8.3 Random\Randomizer::getBytesFromString(). Tokeny ukládejte jako hash, omezte jejich platnost, používejte je jen jednou a porovnávejte je pomocí hash_equals().

Odkaz, který se příliš snadno uhodne

„Zapomněli jste heslo?“ Jedno kliknutí a systém odešle odkaz s dlouhým kódem. Kdo odkaz zná, může nastavit heslo. Právě proto musí být tento kód nepředvídatelný. Pokud není, útočník nepotřebuje ani schránku, ani staré heslo, jen dostatek pokusů.

Při kontrolách aplikací v PHP se překvapivě často najdou tokeny sestavené z času, ID uživatele nebo jednoduchého generátoru náhodných čísel. Vypadají dlouze a náhodně, ale nejsou. Totéž platí pro klíče API, pozvánkové odkazy, odkazy ke sdílení dokumentů, potvrzovací kódy a tokeny CSRF.

Riziko je hmatatelné: předvídatelný odkaz k resetu umožňuje převzít každý účet, jehož e-mailová adresa je známá, aniž by si majitel čehokoli všiml, dokud se sám nepřestane moci přihlásit.

PHP nabízí od verze 7.0 jednoduché a bezpečné řešení. Stačí ho důsledně používat.

random_bytes v PHP: odkud pocházejí bezpečné náhodné hodnoty

random_bytes() poskytuje podle příručky PHP kryptograficky bezpečné náhodné bajty, které se hodí i pro dlouhodobá tajemství, jako jsou klíče. Funkce používá zdroj náhody operačního systému, v Linuxu například getrandom(), ve Windows rozhraní CNG. Pokud nenajde vhodný zdroj, vyhodí od PHP 8.2 výjimku Random\RandomException.

Přehled ukazuje, které funkce se pro tajné hodnoty hodí:

Funkce Vhodná pro tokeny? Poznámka
random_bytes() ano bezpečné bajty, od PHP 7.0
random_int() ano bezpečné číslo v rozsahu, od PHP 7.0
Random\Randomizer s Secure enginem ano Secure je podle příručky výchozí engine
rand(), mt_rand() ne předvídatelné, jen pro účely bez bezpečnostního významu
uniqid() ne založené na čase, určené pro jedinečnost
md5(time()), sha1($id) ne vstup lze uhodnout, hash na tom nic nemění
str_shuffle(), array_rand() ne nepoužívají bezpečný generátor

Vytváření tokenů: délka a kódování

32 náhodných bajtů odpovídá 256 bitům a pro téměř jakýkoli účel je to více než dost. Pro přenos v odkazech nebo hlavičkách se musí token převést na text. Běžné jsou dvě varianty:

<?php
declare(strict_types=1);

// Hex: 64 znaků, jen 0-9 a a-f
$tokenHex = bin2hex(random_bytes(32));

// Base64-URL: 43 znaků, bezpečné v URL bez dalšího kódování
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Náhodné číslo v rozsahu, například pro výběr
$index = random_int(0, 9);

// Od PHP 8.3: šestimístný číselný kód, např. pro potvrzení přes SMS
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);

Krátké číselné kódy jsou obhajitelné jen tehdy, když je počet pokusů přísně omezen a kód po několika minutách vyprší. Pro odkazy rozesílané e-mailem vždy použijte dlouhý token.

U klíčů API, které partneři nebo vlastní systémy používají trvale, se osvědčily dva doplňky. Krátká, neutajovaná předpona před náhodnou částí, například pro prostředí nebo účel, usnadňuje rozpoznání v logech a při nálezech v repozitářích kódu. A klíč se zobrazí jen jednou při vytvoření; poté systém zná už jen jeho hash. Pokud se klíč ztratí, nahradí se, neobnovuje se.

Pozor na vlastnoručně sestavené tokeny, které obsahují informace, například ID uživatele a čas vypršení v Base64. Kódování není zabezpečení: každý může takové tokeny číst a měnit. Pokud má token nést data, musí být podepsán tajným klíčem, například pomocí hash_hmac('sha256', ...), a podpis se ověřuje pomocí hash_equals(). Pro náročnější případy je lepší používat ověřené knihovny než vlastní konstrukce.

Ukládání, ověřování a vypršení tokenů

Bezpečně vytvořený token je jen polovina práce. Stejně důležité je, jak s ním aplikace nakládá. Osvědčený postup pro odkazy k resetu a pozvánky:

  1. Ukládat jen hash: V databázi je hash('sha256', $token), samotný token odchází uživateli jen v odkazu.
  2. Omezit platnost: Dobu vypršení uložit a při uplatnění ověřit.
  3. Použít jednou: Po úspěšném použití záznam smazat nebo označit jako spotřebovaný.
  4. Při změnách zneplatnit: Po změně hesla smazat všechny otevřené tokeny k resetu daného účtu.
  5. Porovnávat v konstantním čase: Kde se token přímo porovnává s uloženou hodnotou, použít hash_equals() s uloženou hodnotou jako prvním parametrem.
<?php
// Vytvoření
$token = bin2hex(random_bytes(32));
$eintrag = [
    'konto_id'   => $kontoId,
    'token_hash' => hash('sha256', $token),
    'gueltig_bis'=> (new DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s'),
];
// $token odeslat jen v odkazu, $eintrag uložit

// Uplatnění
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// Záznam vyhledat pomocí prepared statement podle token_hash,
// zkontrolovat vypršení, poté záznam okamžitě smazat

Omezte také počet požadavků na reset na účet a časové období, aby funkci nešlo zneužít k hromadnému rozesílání e-mailů. Odpověď na požadavek na reset by navíc neměla prozradit, zda zadaná adresa vůbec má účet.

Rychlý hash jako SHA-256 zde stačí, protože samotný token je už dlouhý a náhodný. Pro hesla, která volí lidé, to neplatí; tam jsou potřeba pomalé postupy, jak vysvětluje článek Bezpečné ukládání hesel v PHP.

Tip

Tokeny nepatří do logů. Zkontrolujte, zda váš webserver loguje úplné URL s parametry dotazu a zda chybové zprávy neodesílají data požadavku.

Po incidentu: obnovte tokeny a klíče

Kdo mohl na serveru spouštět kód, měl zpravidla přístup i k uloženým tokenům, klíčům API a datům relací. Pokud po PHP2Shell nebo jiném incidentu existuje podezření na průnik, měli byste proto obnovit nejen hesla, ale i dlouhodobé tokeny a klíče: přístupy k API, podpisové klíče pro cookies nebo odkazy, přístupy k platebním a dopravním službám.

Relace jsou zvláštní případ: ID relací vytváří PHP samo. Jak zabezpečit jejich nastavení a po incidentu relace ukončit, popisuje příručka Zabezpečení relací v PHP. K tokenům formulářů proti padělaným požadavkům viz Ochrana proti CSRF v aplikacích PHP. Další vestavěné nástroje představuje stránka Vestavěné bezpečnostní funkce PHP.

Jak konkrétně pomáhá PHP SOS?

  • Ve vaší aplikaci najdeme všechna místa, kde se vytvářejí tokeny, kódy a klíče, a nahradíme nebezpečné zdroje.
  • Zkontrolujeme ukládání, vypršení a jednorázové použití odkazů k resetu, pozvánek a sdílení.
  • Postaráme se o to, aby tokeny nekončily v lozích, chybových zprávách ani analytických nástrojích.
  • Po incidentu s vámi naplánujeme uspořádanou výměnu tokenů a klíčů.

Kontrolu vlastního softwaru v PHP nabízíme jako bezpečnostní audit a penetrační test.

Časté dotazy

Proč se mt_rand pro tokeny nehodí?

mt_rand a rand jsou určeny pro statistiku a hry, ne pro tajné hodnoty. Jejich výstupy lze za určitých okolností předpovědět. Pro vše, co útočník nesmí uhodnout, používejte random_bytes nebo random_int.

Jak dlouhý by měl token být?

Pro odkazy k obnovení hesla, pozvánky nebo klíče API je solidní volbou 32 náhodných bajtů, jako text v hexadecimálním zápisu 64 znaků. 16 bajtů, tedy 128 bitů, je rozšířená spodní hranice.

Je uniqid bezpečný token?

Ne. uniqid vychází z aktuálního času a je určen pro jedinečnost, ne pro utajení. Také kombinace jako md5 z času nebo ID uživatele jsou předvídatelné.

Musím tokeny pro reset v databázi šifrovat?

Místo tokenu ukládejte jeho hash, například pomocí SHA-256. Kdo čte databázi, nemůže z něj sestavit platné odkazy. Protože je samotný token už dlouhý a náhodný, nepotřebuje pomalý postup jako u hesel.

Co se stane, když systém nenajde zdroj náhody?

random_bytes a random_int vyhazují od PHP 8.2 výjimku Random\RandomException. Tento případ zachyťte a operaci přerušte, místo abyste přecházeli na slabší zdroj.

Zdroje

Okamžitý kontakt

Každá hodina se počítá. Napište nám hned.

Stručně popište systém, verzi PHP a pozorování. Ozveme se, vyjasníme situaci a budeme pracovat na dálku, dokud nebude incident uzavřen a zdokumentován.

Okamžitá pomoc