Генерування криптографічно захищених токенів


83

Для того, щоб створити 32-символьний маркер для доступу до нашого API, ми зараз використовуємо:

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

Я читав, що цей метод не є криптографічно безпечним, оскільки він базується на системних годинниках, і це openssl_random_pseudo_bytesбуло б кращим рішенням, оскільки важче було б передбачити.

Якщо це так, як би виглядав еквівалентний код?

Я припускаю щось подібне, але я не знаю, чи це правильно ...

$token = md5(openssl_random_pseudo_bytes(32));

Також яка довжина має сенс, яку я повинен передати функції?


4
Чому md5 це все-таки? Просто перетворіть bytestream у hex: ви отримуєте 32 байти назад із openssl_random_pseudo_bytes (), рендеріть кожен із цих байт як шістнадцяткове значення з bin2hex (), як показано в прикладах PHP-документів
Марк Бейкер

Я хочу лише 32 символи? Як би я це зробив?
пожежа

10
md5()генерує 32 символьний рядок, але в ньому є дані лише на 128 біт. openssl_random_pseudo_bytes()повертає справжні двійкові дані, тому має 32 * 8 = 256 біт випадковості. Набиваючи свій 32-байтний випадковий рядок через md5, ви ефективно зменшуєте його унікальність на величезну кількість.
Marc B

1
Отже, $token = bin2hex(openssl_random_pseudo_bytes(16));достатньо, чи мені потрібен цикл з 16 ітерацій, який передає 1 як довжину і додає до шеренги до рядка?
пожежа

Остаточне рішення з 16 байтами, перетвореними у шістнадцяткову форму, є правильним. Але вам не варто покладатися на OpenSSL тут # 1 # 2 # 3 . Як тільки ви використовуєте PHP 7.0+, насправді вже немає виправдання, щоб використовувати його. Замість OpenSSL та шістнадцяткового кодування спробуйте Random::alphanumericString($length), що вміщує приблизно в 2 мільярди разів більше “ентропії” в цих 16 символах.
caw

Відповіді:


187

Ось правильне рішення:

$token = bin2hex(openssl_random_pseudo_bytes(16));

# or in php7
$token = bin2hex(random_bytes(16));

1
Я додав ще одну відповідь про те, як ви можете створити унікальний маркер, використовуючи openssl_random_pseudo_bytes за допомогою CryptoLib. Це дозволяє генерувати маркери з усіма символами, на відміну від лише 0-9, AF.
mjsa

4
Підтримка PHP 5.x для random_bytes () та random_int () github.com/paragonie/random_compat
MTK

4
Ви також можете захотіти, bin2hex(random_bytes($length/2))тому що у вас може бути 2 шістнадцяткові символи для кожного байта, тому кількість символів завжди буде подвоєною $lengthбез нього
Друг

3
@AFriend погодився. Якщо ви хочете створити функцію, яка створює рядок із $lengthкількості символів (а не дбає про розмір байта), вам потрібно буде передати random_bytes($length/2).
BadHorsie

10

Якщо ви хочете використовувати openssl_random_pseudo_bytes, найкраще скористатися реалізацією CrytoLib, це дозволить вам генерувати всі буквено-цифрові символи, якщо наклеювання bin2hex навколо openssl_random_pseudo_bytes просто призведе до отримання AF (заголовків) та цифр.

Замініть шлях / до / місцем, куди ви поклали файл cryptolib.php (його можна знайти на GitHub за адресою: https://github.com/IcyApril/CryptoLib )

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

Повна документація CryptoLib доступна за адресою: https://cryptolib.ju.je/ . Він охоплює багато інших випадкових методів, каскадне шифрування та генерування та перевірку хешу; але це є, якщо вам це потрібно.


Для однакової кількості байтів: openssl_random_pseudo_bytes(16)кожен байт може мати будь-яке значення (0-255), тоді як randomString(16)кожен байт може мати лише az Az 0-9, що становить 62 комбінації на байт. Yup randomString краще на довжину рядка , оскільки bin2hex є неефективним кодуванням (50%); краще використовувати base64_encode(openssl_random_pseudo_bytes(16))для коротшого рядка і точно відомої кількості байтів безпеки (16 у цьому випадку, але змінювати за необхідності)
Родні

1
@Rodney Майте у вигляді підставі 64 також буде виробляти рядок , що містить +, /і =символи, так що якщо ви намагаєтеся сформувати зручний маркер (наприклад , ключ API) він не ідеальний.
BadHorsie

9

Якщо у вас є криптографічно захищений генератор випадкових чисел, вам не потрібно хешувати його вихідні дані. Насправді ви цього не хочете. Просто використовуйте

$token  = openssl_random_pseudo_bytes($BYTES,true)

Де $ BYTES - це, однак, багато байт даних, які ви хочете. MD5 має 128-бітний хеш, тож підійде 16 байт.

Як додаткове зауваження, жодна з функцій, які ви викликаєте у своєму вихідному коді, не є криптографічно безпечною, більшість з них достатньо шкідливі, так що використання лише однієї з них може бути небезпечним, навіть якщо поєднуватись із захищеними іншими функціями. MD5 має проблеми з безпекою (хоча для цієї програми вони можуть бути не актуальними). Uniqid не просто не генерує криптографічно випадкові байти за замовчуванням (оскільки він використовує системний годинник), додана ентропія, яку ви передаєте, поєднується за допомогою лінійного конгруентного генератора, який не є криптографічно безпечним. Насправді це, мабуть, означає, що можна здогадатися про всі ваші ключі API, отримавши доступ до кількох з них, навіть якщо вони навіть не підозрювали про значення годинника вашого сервера. Нарешті, mt_rand (), що ви використовуєте як додаткову ентропію, теж не є надійним генератором випадкових чисел.


27
Другим аргументом openssl_random_pseudo_bytes () має бути змінна, передана за посиланням. Після openssl_random_pseudo_bytes ця змінна буде істинною або хибною, якщо openssl зміг використовувати криптографічно сильний алгоритм. Це не використовується, щоб сказати openssl використовувати криптографічно сильний алгоритм, який він спробує зробити в будь-якому випадку.
Jonathan Amend

-1

Надійні паролі Ви можете створювати лише символи ascii a-zA-Z та цифри 0-9 . Для цього найкращим чином використовується лише криптографічно безпечні методи, такі як random_int () або random_bytes () від PHP7. Функції відпочинку як base64_encode () Ви можете використовувати лише як функції підтримки, щоб забезпечити надійність рядка та змінити його на символи ASCII .

mt_rand () не є безпечним і дуже старий. З будь-якого рядка Ви повинні використовувати random_int (). З двійкового рядка Ви повинні використовувати base64_encode (), щоб зробити двійковий рядок надійним або bin2hex, але тоді Ви скоротите байт лише до 16 позицій (значень). Подивіться мою реалізацію цих функцій. Він використовує всі мови.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.