# Bezpieczne liczby losowe i tokeny w PHP z random_bytes

> Bezpieczne tokeny w PHP z random_bytes i random_int: długość, kodowanie, przechowywanie jako skrót, czas ważności i funkcje, których należy unikać.

Źródło: https://phpsos.de/pl/blog/sichere-tokens-php/  
Stan: 25.10.2026  
Dostawca: PHP SOS, Pomoc awaryjna PHP i bezpieczeństwo serwerów. Natychmiastowa pomoc przez WhatsApp: https://wa.me/491736494914?text=Dzie%C5%84%20dobry%2C%20PHP%20SOS%2C%20potrzebujemy%20pomocy%20z%20naszym%20serwerem%20PHP.%20Firma%3A%20%E2%80%A6%20Strona%20internetowa%3A%20%E2%80%A6

**Krótko:** Bezpieczne tokeny generujesz w PHP za pomocą `random_bytes()`, na przykład 32 bajty, które zamieniasz na tekst za pomocą `bin2hex()` lub Base64-URL. Do liczb losowych z zakresu służy `random_int()`, a do kodów ze stałego zestawu znaków od PHP 8.3 `Random\Randomizer::getBytesFromString()`. Tokeny przechowujesz jako skrót, ograniczasz ich ważność, używasz ich tylko raz i porównujesz za pomocą `hash_equals()`.

## Link, który zbyt łatwo odgadnąć

„Nie pamiętasz hasła?” Jedno kliknięcie i system wysyła link z długim kodem. Kto zna link, może ustawić hasło. Właśnie dlatego ten kod musi być nieprzewidywalny. Jeśli nie jest, atakujący nie potrzebuje ani skrzynki pocztowej, ani starego hasła, a tylko dostatecznie wielu prób.

Podczas kontroli aplikacji PHP zaskakująco często znajdują się tokeny zbudowane z godziny, identyfikatora użytkownika lub prostego generatora liczb losowych. Wyglądają na długie i losowe, ale takie nie są. To samo dotyczy kluczy API, linków z zaproszeniami, linków do udostępniania dokumentów, kodów potwierdzających i tokenów CSRF.

Ryzyko jest realne: przewidywalny link resetujący umożliwia przejęcie każdego konta, którego adres e-mail jest znany, i właściciel niczego nie zauważa, dopóki sam nie może się już zalogować.

PHP oferuje od wersji 7.0 proste i bezpieczne rozwiązanie. Trzeba tylko konsekwentnie z niego korzystać.

## random_bytes w PHP: skąd biorą się bezpieczne wartości losowe

`random_bytes()` zwraca według podręcznika PHP kryptograficznie bezpieczne bajty losowe, które nadają się także do długowiecznych sekretów, takich jak klucze. Funkcja używa źródła losowości systemu operacyjnego, w Linuksie na przykład `getrandom()`, w Windows interfejsu CNG. Jeśli nie znajdzie odpowiedniego źródła, zgłasza od PHP 8.2 wyjątek `Random\RandomException`.

Zestawienie pokazuje, które funkcje nadają się do sekretów:

| Funkcja | Nadaje się na tokeny? | Wskazówka |
|---|---|---|
| `random_bytes()` | tak | bezpieczne bajty, od PHP 7.0 |
| `random_int()` | tak | bezpieczna liczba z zakresu, od PHP 7.0 |
| `Random\Randomizer` z silnikiem Secure | tak | Secure jest według podręcznika silnikiem domyślnym |
| `rand()`, `mt_rand()` | nie | przewidywalne, tylko do celów niezwiązanych z bezpieczeństwem |
| `uniqid()` | nie | oparte na czasie, przeznaczone do zapewnienia unikalności |
| `md5(time())`, `sha1($id)` | nie | dane wejściowe da się odgadnąć, skrót tego nie zmienia |
| `str_shuffle()`, `array_rand()` | nie | nie używają bezpiecznego generatora |

## Generowanie tokenów: długość i kodowanie

32 losowe bajty to 256 bitów i do prawie każdego celu są więcej niż wystarczające. Do przesyłania w linkach lub nagłówkach token trzeba zamienić na tekst. Powszechne są dwa warianty:

```php
<?php
declare(strict_types=1);

// Hex: 64 znaki, tylko 0-9 i a-f
$tokenHex = bin2hex(random_bytes(32));

// Base64-URL: 43 znaki, bezpieczne w adresach URL bez dodatkowego kodowania
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Liczba losowa z zakresu, na przykład do wyboru
$index = random_int(0, 9);

// Od PHP 8.3: sześciocyfrowy kod liczbowy, np. do potwierdzenia przez SMS
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);
```

Krótkie kody liczbowe są uzasadnione tylko wtedy, gdy liczba prób jest ściśle ograniczona, a kod wygasa po kilku minutach. Dla linków wysyłanych e-mailem zawsze używaj długiego tokenu.

Dla kluczy API, z których partnerzy lub własne systemy korzystają trwale, sprawdziły się dwa uzupełnienia. Krótki, niejawny prefiks przed częścią losową, na przykład dla środowiska lub celu, ułatwia rozpoznanie w logach i przy znalezieniu w repozytoriach kodu. A klucz jest wyświetlany tylko raz przy tworzeniu; potem system zna już tylko jego skrót. Jeśli klucz zostanie zgubiony, jest wymieniany, a nie odtwarzany.

Ostrożnie z własnoręcznie budowanymi tokenami, które zawierają informacje, na przykład identyfikator użytkownika i czas wygaśnięcia w Base64. Kodowanie nie jest zabezpieczeniem: każdy może takie tokeny odczytać i zmienić. Jeśli token ma przenosić dane, musi być podpisany tajnym kluczem, na przykład za pomocą `hash_hmac('sha256', ...)`, a podpis sprawdza się za pomocą `hash_equals()`. W bardziej złożonych przypadkach sprawdzone biblioteki są lepsze niż własne konstrukcje.

## Przechowywanie, sprawdzanie i wygaszanie tokenów

Bezpiecznie wygenerowany token to tylko połowa sukcesu. Co najmniej tak samo ważne jest to, jak aplikacja się z nim obchodzi. Sprawdzone postępowanie dla linków resetujących i zaproszeń:

1. **Przechowywać tylko skrót:** W bazie danych znajduje się `hash('sha256', $token)`, sam token trafia do użytkownika wyłącznie w linku.
2. **Ograniczyć ważność:** Zapisać czas wygaśnięcia i sprawdzać go przy realizacji.
3. **Użyć jednokrotnie:** Po udanym użyciu usunąć wpis lub oznaczyć jako zużyty.
4. **Unieważniać przy zmianach:** Po zmianie hasła usunąć wszystkie otwarte tokeny resetujące konta.
5. **Porównywać w stałym czasie:** Tam, gdzie token jest porównywany bezpośrednio z zapisaną wartością, użyć `hash_equals()` z zapisaną wartością jako pierwszym parametrem.

```php
<?php
// Generowanie
$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 wysłać tylko w linku, $eintrag zapisać

// Realizacja
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// Wyszukać wpis przez prepared statement po token_hash,
// sprawdzić wygaśnięcie, następnie natychmiast usunąć wpis
```

Ogranicz ponadto liczbę żądań resetu na konto i okres, aby funkcja nie mogła zostać nadużyta do masowej wysyłki e-maili. Odpowiedź na żądanie resetu nie powinna też zdradzać, czy podany adres w ogóle ma konto.

Szybki skrót, taki jak SHA-256, wystarcza tu, ponieważ sam token jest już długi i losowy. Dla haseł wybieranych przez ludzi tak nie jest; tam potrzebne są powolne metody, jak wyjaśnia artykuł [Bezpieczne przechowywanie haseł w PHP](https://phpsos.de/pl/blog/passwoerter-in-php-speichern/).

> **Wskazówka:**
> Tokeny nie należą do plików logów. Sprawdź, czy Państwa serwer WWW zapisuje w logach pełne adresy URL z parametrami zapytania i czy raporty błędów dołączają dane żądania.

## Po incydencie: odnowić tokeny i klucze

Kto mógł wykonywać kod na serwerze, miał z reguły także dostęp do zapisanych tokenów, kluczy API i danych sesji. Jeśli po PHP2Shell lub innym incydencie istnieje podejrzenie włamania, należy więc odnowić nie tylko hasła, ale także długowieczne tokeny i klucze: dostępy API, klucze podpisujące ciasteczka lub linki, dostępy do usług płatności i wysyłki.

Sesje są przypadkiem szczególnym: identyfikatory sesji generuje samo PHP. Jak zabezpieczyć ich ustawienia i kończyć sesje po incydencie, opisuje poradnik [Zabezpieczanie sesji w PHP](https://phpsos.de/pl/ratgeber/session-sicherheit-php/). O tokenach formularzy przeciw sfałszowanym żądaniom zob. [Ochrona przed CSRF w aplikacjach PHP](https://phpsos.de/pl/blog/csrf-schutz-php/). Dalsze wbudowane narzędzia przedstawia artykuł [Wbudowane funkcje bezpieczeństwa PHP](https://phpsos.de/pl/php/eingebaute-sicherheitsfunktionen/).

## Jak konkretnie pomaga PHP SOS?

- Znajdujemy w Państwa aplikacji wszystkie miejsca, w których generowane są tokeny, kody i klucze, i zastępujemy niebezpieczne źródła.
- Sprawdzamy przechowywanie, wygasanie i jednokrotne użycie linków resetujących, zaproszeń i udostępnień.
- Dbamy o to, aby tokeny nie trafiały do logów, raportów błędów ani narzędzi analitycznych.
- Po incydencie planujemy z Państwem uporządkowaną wymianę tokenów i kluczy.

Kontrolę własnego oprogramowania PHP oferujemy jako [audyt bezpieczeństwa i test penetracyjny](https://phpsos.de/pl/leistungen/sicherheitsaudit-penetrationstest/).


## Częste pytania

**Dlaczego mt_rand nie nadaje się na tokeny?**  
mt_rand i rand są przeznaczone do statystyki i gier, a nie do sekretów. Ich wyniki mogą w pewnych okolicznościach zostać przewidziane. Do wszystkiego, czego atakujący nie może odgadnąć, użyj random_bytes lub random_int.

**Jak długi powinien być token?**  
Dla linków do resetowania haseł, zaproszeń lub kluczy API solidnym wyborem jest 32 losowe bajty, w postaci tekstu szesnastkowego 64 znaki. 16 bajtów, czyli 128 bitów, to powszechna dolna granica.

**Czy uniqid to bezpieczny token?**  
Nie. uniqid opiera się na bieżącym czasie i służy do zapewnienia unikalności, a nie do zachowania tajemnicy. Także kombinacje, takie jak md5 z czasu lub identyfikatora użytkownika, są przewidywalne.

**Czy muszę szyfrować tokeny resetujące w bazie danych?**  
Zamiast tokenu przechowuj jego skrót, na przykład za pomocą SHA-256. Kto odczyta bazę danych, nie zbuduje dzięki temu ważnych linków. Ponieważ sam token jest już długi i losowy, nie potrzeba powolnej metody jak w przypadku haseł.

**Co się stanie, jeśli system nie znajdzie źródła losowości?**  
random_bytes i random_int zgłaszają od PHP 8.2 wyjątek Random\RandomException. Przechwyć ten przypadek i przerwij operację, zamiast sięgać po słabsze źródło.

## Źródła

- [Podręcznik PHP: random_bytes](https://www.php.net/manual/de/function.random-bytes.php)
- [Podręcznik PHP: Random\Randomizer::getBytesFromString](https://www.php.net/manual/de/random-randomizer.getbytesfromstring.php)
- [Podręcznik PHP: hash_equals](https://www.php.net/manual/de/function.hash-equals.php)
- [Seria ściągawek OWASP (Cheat Sheet Series)](https://cheatsheetseries.owasp.org/)
