Zdecydowana większość programistów i programistek chociaż raz wykorzystała funkcję do generowania losowej liczby. Czy to do testów, prostej gry, czy tasowania elementów w tablicy - Math.random() (i jego odpowiedniki w innych językach programowania) jest często pierwszym wyborem.
Ale czy kiedykolwiek zastanawiałeś/aś się, jak naprawdę działa losowość w świecie komputerów i dlaczego nie zawsze jest ona tak losowa, jak nam się wydaje?
Komputer, czyli maszyna deterministyczna
Aby w pełni zrozumieć problem, musimy zacząć od fundamentalnej prawdy: komputery z natury są maszynami deterministycznymi. Oznacza to, że dla tego samego zestawu danych wejściowych, zawsze wyprodukują ten sam wynik. Nie ma tu miejsca na przypadek. Skąd więc biorą się liczby losowe?
Okazuje się, że w większości przypadków mamy do czynienia z iluzją losowości. Funkcje, których używamy na co dzień, w rzeczywistości korzystają ze złożonych algorytmów do generowania sekwencji liczb, które tylko wyglądają na przypadkowe. To prowadzi nas do pierwszego i najpopularniejszego rodzaju “losowości”.
Pseudo-losowość, czyli kontrolowany chaos (PRNG)
Większość języków programowania, w tym JavaScript ze swoim Math.random(), wykorzystuje pseudolosowe generatory Liczb (ang. Pseudo-Random Number Generators, w skrócie PRNG). Są to algorytmy, które na podstawie wartości początkowej, zwanej ziarnem (ang. seed), generują długą sekwencję liczb.
Kluczowe jest tu słowo “ziarno”. Jeśli znamy algorytm i ziarno, które zostało użyte na początku, jesteśmy w stanie odtworzyć całą, identyczną sekwencję z pozoru losowych liczb.
W przypadku Math.random() w przeglądarkach, ziarno jest zazwyczaj ustawiane automatycznie przy starcie, na przykład na podstawie aktualnego czasu. To sprawia, że przy każdym uruchomieniu sekwencja jest inna, ale nadal jest w pełni deterministyczna.
Kiedy PRNG wystarczy?
Generatory pseudolosowe są szybkie i w zupełności wystarczające w wielu sytuacjach, takich jak:
Gry komputerowe: do losowego rozmieszczania przeciwników czy generowania map (zdecydowanie nie jest to wystarczająca "losowość" do logiki gier hazardowych!).
Symulacje i modelowanie: np. w badaniach naukowych.
Algorytmy: gdzie losowość pomaga poprawić wydajność, o ile nie zależy od niej bezpieczeństwo.
Problem pojawia się, gdy potrzebujemy nieprzewidywalności. Użycie standardowego PRNG do generowania haseł, tokenów sesji czy kluczy kryptograficznych byłoby ogromną luką bezpieczeństwa.
Kryptograficznie bezpieczna losowość (CSPRNG)
Poza pseudolosowością jest jeszcze jedna, nieco bardziej bezpieczna możliwość. Gdy w grę wchodzi bezpieczeństwo, musimy sięgnąć po cięższy kaliber. Kryptograficznie bezpieczne pseudolosowe generatory liczb (ang. Cryptographically Secure Pseudorandom Number Generators, CSPRNG) to specjalna klasa generatorów zaprojektowana z myślą o nieprzewidywalności. Można ją spotkać także pod nazwą CRNG, czyli Cryptographic Random Number Generator.
Jak tego zatem dokonują, skoro komputer nie potrafi wygenerować losowej liczby? Otóż zamiast prostego ziarna (stosowanego w PRNG), CSPRNG czerpią tzw. entropię (co można rozumieć jako zmienność) z wielu nieprzewidywalnych źródeł w systemie operacyjnym. Mogą to być na przykład:
Ruchy myszką i wciśnięcia klawiszy
Szum z mikrofonu lub kamery
Opóźnienia w operacjach sieciowych
Precyzyjny zegar systemowy
Dzięki temu nawet jeśli atakujący zna stan systemu, odgadnięcie kolejnej wygenerowanej liczby jest praktycznie niemożliwe.
W JavaScript (w środowisku przeglądarkowym) mamy dostęp do CSPRNG za pomocą Web Crypto API:
W Node.js podobną funkcjonalność oferuje moduł crypto.
Kiedy w takim razie stosować CSPRNG? Odpowiedź jest prosta. Zawsze, gdy losowość ma wpływ na bezpieczeństwo. Przykładami mogą być:
Generowanie soli do hashowania haseł.
Tworzenie identyfikatorów sesji i tokenów.
Generowanie kluczy kryptograficznych.
Generowanie wartości, których nikt nie jest w stanie odgadnąć.
Większość z nas korzysta z dobrodziejstw CRNG codziennie - przeglądarki internetowe wykorzystują tę technikę do tworzenia bezpiecznego połączenia pomiędzy klientem, a serwerem. Efektem tego działania jest ikona kłódki, którą widzimy obok adresu strony. Oznacza ona, że połączenie jest szyfrowane z wykorzystaniem TLS, czyli Transport Security Layer. Takie szyfrowanie wymaga "klucza", który będzie stosowany do szyfrowania i deszyfrowania informacji wymienianych pomiędzy serwerem i przeglądarką. To właśnie do utworzenia tego klucza wykorzystuje się zazwyczaj generatory typu CSPRNG.
Prawdziwa losowość z natury
Czy w programowaniu istnieje zatem prawdziwa losowość? Czy jesteśmy w ogóle w stanie wygenerować prawdziwie losową liczbę?
Okazuje się, że tak, ale na próżno szukać takiej losowości w algorytmach. Prawdziwe generatory liczb losowych (ang. True Random Number Generators, TRNG) to zazwyczaj fizyczne urządzenia, które czerpią losowość ze zjawisk naturalnych i wykorzystują procesy, które z natury są zupełnie nieprzewidywalne. Przykładami mogą być:
Szum atmosferyczny: słuchanie zakłóceń radiowych w atmosferze.
Szum termiczny: losowe ruchy elektronów w układach elektronicznych.
Zjawiska kwantowe: np. rozpad radioaktywny.
Takie generatory najczęściej wykorzystuje się w systemach o najwyższych wymaganiach bezpieczeństwa, w zaawansowanej kryptografii czy w legalnych systemach gier hazardowych online. Dla przeciętnego programisty są one raczej ciekawostką, ale pokazują, jak głęboko sięga problem generowania prawdziwej losowości.
Jakiego generatora użyć?
Wybór odpowiedniego narzędzia zależy wyłącznie od kontekstu. W dużym uproszczeniu można to ująć w następujący sposób:
Rodzaj generatora | Główne cechy | Kiedy używać? | Przykład |
|---|---|---|---|
PRNG | Szybki, deterministyczny, oparty na ziarnie. | Gry, symulacje, testy, animacje. | Math.random() |
CSPRNG | Wolniejszy, nieprzewidywalny, czerpie entropię z systemu. | Kryptografia, hasła, tokeny, bezpieczeństwo. | crypto.getRandomValues() |
TRNG | Sprzętowy, oparty na zjawiskach fizycznych, najwolniejszy. | Najwyższy poziom bezpieczeństwa, nauka, hazard. | Wymaga specjalnego sprzętu |
Błędny wybór może prowadzić do poważnych konsekwencji, czego świetnym przykładem jest luka bezpieczeństwa, która istniała w oprogramowaniu OpenSSL dystrybuowanym przez system operacyjny Debian. Przez ponad dwa lata biblioteka, która miała zapewniać kryptograficzną losowość (a co za tym idzie bezpieczeństwo) opierała się jedynie na id procesu, który w przypadku większości systemów mieści się w przedziale od 1 do 32767. Oznacza to, że ziarno użyte do wygenerowania kluczy SSH, certyfikatów SSL i innych rodzajów zabezpieczeń nie były wystarczająco losowe. Atakujący mógł po prostu wygenerować wszystkie możliwe kombinacje i uzyskać dostęp do “zabezpieczonych” w ten sposób miejsc. Wszystko to przez błąd programisty, który usunął kod odpowiedzialny za źródło entropii.
Najważniejszą lekcją, która płynie z tego artykułu, jest fakt, że Math.random() nie nadaje się do celów związanych z bezpieczeństwem. Wiedza o tym, co kryje się za prostą funkcją losową, pozwala nam podejmować lepsze, bardziej świadome decyzje i tworzyć bezpieczniejsze aplikacje.





