Aktualna sytuacja: PHP2Shell. Dwie krytyczne luki w PHP (CVSS 9,2) umożliwiają wykonanie złośliwego kodu bez logowania. Poprawki od 21.10.2026.Co należy teraz zrobić →

Bezpieczne liczby losowe i tokeny w PHP z random_bytes

Jak programiści generują w PHP tokeny dla linków do resetu hasła, kluczy API i kodów jednorazowych: kryptograficznie bezpieczne źródła, wystarczająca długość, przechowywanie jako skrót i ograniczona ważność.

Stan 5 min czytaniaBezpieczne programowanie

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().

„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
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
// 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.

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. O tokenach formularzy przeciw sfałszowanym żądaniom zob. Ochrona przed CSRF w aplikacjach PHP. Dalsze wbudowane narzędzia przedstawia artykuł Wbudowane funkcje bezpieczeństwa PHP.

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.

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

Kontakt natychmiast

Liczy się każda godzina. Proszę napisać do nas teraz.

Proszę krótko opisać system, wersję PHP i obserwacje. Skontaktujemy się z Państwem, ustalimy sytuację i będziemy pracować zdalnie, aż incydent zostanie zamknięty i udokumentowany.

Natychmiastowa pomoc