Generowanie tokenów bezpiecznych kryptograficznie

84

W celu wygenerowania 32-znakowego tokena dostępu do naszego API używamy obecnie:

$token = md5(uniqid(mt_rand(), true));

Czytałem, że ta metoda nie jest kryptograficznie bezpieczna, ponieważ opiera się na zegarze systemowym, a to openssl_random_pseudo_bytesbyłoby lepsze rozwiązanie, ponieważ byłoby trudniejsze do przewidzenia.

W takim przypadku jak wyglądałby równoważny kod?

Zakładam coś takiego, ale nie wiem, czy to prawda ...

$token = md5(openssl_random_pseudo_bytes(32));

Jaka długość ma sens, którą powinienem przekazać funkcji?

ogień
źródło
4
Dlaczego jednak MD5 to? Po prostu przekonwertuj strumień bajtowy na hex: otrzymujesz 32 bajty z powrotem z openssl_random_pseudo_bytes (), wyrenderuj każdy z tych bajtów jako wartość szesnastkową za pomocą bin2hex (), jak pokazano w przykładach dokumentacji PHP
Mark Baker
Chcę tylko 32 znaków? Jak miałbym to zrobić?
pożar
10
md5()generuje 32-znakowy ciąg, ale zawiera tylko 128-bitowe dane. openssl_random_pseudo_bytes()zwraca prawdziwe dane binarne, więc ma 32 * 8 = 256 bitów losowości. Wypychając 32-bajtowy losowy ciąg przez md5, skutecznie zmniejszasz jego wyjątkowość o ogromną ilość.
Marc B
1
Czy więc jest $token = bin2hex(openssl_random_pseudo_bytes(16));wystarczająca, czy potrzebuję pętli 16 iteracji przekazujących 1 jako długość i dołączanych szesnastkowo do ciągu?
pożar
Ostateczne rozwiązanie z 16 bajtami przekonwertowanymi na hex jest poprawne. Ale tak naprawdę nie powinieneś polegać na OpenSSL tutaj # 1 # 2 # 3 . Gdy tylko zaczniesz korzystać z PHP 7.0+, naprawdę nie ma już wymówki, aby go używać. Zamiast OpenSSL i kodowania szesnastkowego spróbuj Random::alphanumericString($length), które mieści około 2 miliardy razy więcej „entropii” w tych 16 znakach.
krakanie

Odpowiedzi:

189

Oto poprawne rozwiązanie:

$token = bin2hex(openssl_random_pseudo_bytes(16));

# or in php7
$token = bin2hex(random_bytes(16));
ogień
źródło
1
Dodałem kolejną odpowiedź, w jaki sposób można wygenerować unikalny token za pomocą openssl_random_pseudo_bytes z CryptoLib. Pozwala to na generowanie tokenów ze wszystkimi znakami, a nie tylko 0-9, AF.
mjsa
4
Obsługa PHP 5.x dla random_bytes () i random_int () github.com/paragonie/random_compat
MTK
4
Możesz również chcieć, bin2hex(random_bytes($length/2))ponieważ możesz mieć 2 znaki szesnastkowe na każdy bajt, więc liczba znaków będzie zawsze podwojona $lengthbez tego
Przyjaciel
3
@AFriend zgadza się. Jeśli chcesz utworzyć funkcję, która generuje ciąg $lengthliczby znaków (zamiast dbać o rozmiar bajtu), będziesz chciał ją przekazać random_bytes($length/2).
BadHorsie
10

Jeśli chcesz użyć openssl_random_pseudo_bytes, najlepiej użyć implementacji CrytoLib, pozwoli ci to wygenerować wszystkie znaki alfanumeryczne, przyklejanie bin2hex wokół openssl_random_pseudo_bytes spowoduje po prostu uzyskanie AF (kapsli) i liczb.

Zastąp ścieżkę / do / miejscem, w którym umieściłeś plik cryptolib.php (możesz go znaleźć na GitHub pod adresem : https://github.com/IcyApril/CryptoLib )

<?php
  require_once('path/to/cryptolib.php');
  $token = IcyApril\CryptoLib::randomString(16);
?>

Pełna dokumentacja CryptoLib jest dostępna pod adresem : https://cryptolib.ju.je/ . Obejmuje wiele innych losowych metod, szyfrowanie kaskadowe oraz generowanie i walidację skrótów; ale jest tam, jeśli tego potrzebujesz.

mjsa
źródło
Dla tej samej liczby bajtów: openssl_random_pseudo_bytes(16)każdy bajt może mieć dowolną wartość (0-255), podczas gdy randomString(16)każdy bajt może mieć tylko az Az 0-9, czyli 62 kombinacje na bajt. Tak, randomString jest lepsze dla każdej długości łańcucha , ponieważ bin2hex jest nieefektywne kodowanie (50%); lepiej użyć base64_encode(openssl_random_pseudo_bytes(16))dla krótszego ciągu i dokładnej znanej liczby bajtów bezpieczeństwa (w tym przypadku 16, ale zmień w razie potrzeby)
Rodney
1
@Rodney Niedźwiedź w bazie umysłu 64 będzie również produkować ciąg zawierający +, /i =znaków, więc jeśli próbujesz wygenerować przyjazny dla użytkownika tokena (np klucz API) nie jest idealny.
BadHorsie
9

Jeśli masz bezpieczny kryptograficznie generator liczb losowych, nie musisz haszować jego danych wyjściowych. W rzeczywistości nie chcesz. Po prostu użyj

$token  = openssl_random_pseudo_bytes($BYTES,true)

Gdzie $ BYTES to dowolna liczba bajtów danych. MD5 ma 128-bitowy hash, więc wystarczy 16 bajtów.

Na marginesie, żadna z funkcji, które wywołujesz w oryginalnym kodzie, nie jest bezpieczna kryptograficznie, większość jest na tyle szkodliwa, że ​​użycie tylko jednej z nich byłoby niebezpieczne, nawet w połączeniu z innymi bezpiecznymi funkcjami. MD5 ma problemy z bezpieczeństwem (chociaż w przypadku tej aplikacji mogą one nie mieć zastosowania). Uniqid nie tylko domyślnie nie generuje kryptograficznie losowych bajtów (ponieważ używa zegara systemowego), ale dodana entropia, którą przekazujesz, jest łączona za pomocą liniowego generatora przystającego, który nie jest bezpieczny kryptograficznie. W rzeczywistości prawdopodobnie oznacza to, że można by odgadnąć wszystkie klucze API, mając dostęp do kilku z nich, nawet jeśli nie mieliby pojęcia o wartości zegara serwera. Wreszcie, mt_rand (), której używasz jako dodatkowej entropii, również nie jest bezpiecznym generatorem liczb losowych.

imichaelmiers
źródło
27
Drugi argument metody openssl_random_pseudo_bytes () powinna być zmienną przekazaną przez referencję. Po openssl_random_pseudo_bytes ta zmienna będzie miała wartość true lub false, jeśli openssl był w stanie użyć silnego kryptograficznie algorytmu. Nie jest używany do nakazania openssl użycia silnego algorytmu kryptograficznego, co i tak będzie próbowało zrobić.
Jonathan Amend
-1

Wiarygodne hasła Możesz tworzyć tylko znaki ascii a-zA-Z i cyfry 0-9 . Aby to zrobić, najlepiej jest używać tylko metod bezpiecznych kryptograficznie, takich jak random_int () lub random_bytes () z PHP7. Pozostałe funkcje jak base64_encode () Możesz używać tylko jako funkcji pomocniczych, aby zwiększyć wiarygodność łańcucha i zmienić go na znaki ASCII .

mt_rand () nie jest bezpieczna i jest bardzo stara. Z dowolnego ciągu Musisz użyć random_int (). Z łańcucha binarnego Powinieneś użyć base64_encode (), aby łańcuch binarny był niezawodny lub bin2hex, ale wtedy ograniczysz bajt tylko do 16 pozycji (wartości). Zobacz moją implementację tej funkcji. Używa wszystkich języków.

Daro z Polski
źródło