# Bezpečná náhodná čísla a tokeny v PHP pomocí random_bytes

> Bezpečné tokeny v PHP pomocí random_bytes a random_int: délka, kódování, ukládání jako hash, doba platnosti a funkce, kterým se máte vyhnout.

Zdroj: https://phpsos.de/cs/blog/sichere-tokens-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**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
<?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
<?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](https://phpsos.de/cs/blog/passwoerter-in-php-speichern/).

> **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](https://phpsos.de/cs/ratgeber/session-sicherheit-php/). K tokenům formulářů proti padělaným požadavkům viz [Ochrana proti CSRF v aplikacích PHP](https://phpsos.de/cs/blog/csrf-schutz-php/). Další vestavěné nástroje představuje stránka [Vestavěné bezpečnostní funkce PHP](https://phpsos.de/cs/php/eingebaute-sicherheitsfunktionen/).

## 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](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Č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

- [Příručka PHP: random_bytes](https://www.php.net/manual/de/function.random-bytes.php)
- [Příručka PHP: Random\Randomizer::getBytesFromString](https://www.php.net/manual/de/random-randomizer.getbytesfromstring.php)
- [Příručka PHP: hash_equals](https://www.php.net/manual/de/function.hash-equals.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
