Неожиданное поведение openssl_random_pseudo_bytes() приводящее к фатальной потере крипто-стойкости

Доброго времени суток всем.
688811cc554d471482422bd2a789cb59.png
Недавно в одном из проектов мы столкнулись со следующей проблемой — функция openssl_random_pseudo_bytes () выдавала дублирующиеся псевдослучайные последовательности!

Этого не может быть, потому что этого не может быть никогда! — Скажет любой, кто читал документацию этой функции. И, да, $crypto_strong исправно выдавал TRUE.

И тем не менее — ошибки уникальности при вставке в базу сыпались пачками и лог подтверждал — 32-байтные последовательности генерировались повторно через разные интервалы, от суток до недели. Расследование заняло целый месяц. Сейчас я на 99% уверен, что причина найдена —, но буду благодарен, если Хабражители подтвердят или опровергнут мои выводы.

А дело было в сочетании особенностей сразу трех продуктов:

  • Apache работающего с prefork MPM
  • PHP имеющего ограниченную поддержку функций OpenSSL
  • И самой библиотеки OpenSSL имеющий проблему Random fork-safety


Упрощенно происходящее выглядит так — Апач при старте создает первую копию ПХП которая стартует рандом-генератор OpenSSL. А дальше — Апач создает и использует форки, копируя в том числе и исходное состояние рандом-генератора.
Так как рандом генератор завязан еще и на PID процесса — то проблема проявляется не сразу. Поскольку на Linux типовое максимальное значение для PID 65536, то вот примерно через такое количество запросов к веб-серверу выдаваемые псевдослучайные последовательности и начнут повторяться. Больше точных технических подробностей лучше получить в уже приведенной выше статье базы знаний OpenSLL

Проблема усугубляется тем, что самые лучшие рекомендованные методы борьбы (Call RAND_seed after a fork и Call RAND_poll after a fork) на ПХП неприменимы, так как эти функции OpenSSL попросту недоступны из ПХП.

К сожалению, мне не удалось найти в сети адекватных материалов по этой проблеме, за исключением уже приведенной статьи OpenSLL, но она не описывает конкретную связку Apache + PHP + OpenSSL. Зато статей настоятельно рекомендующих использовать openssl_random_pseudo_bytes () как криптостойкий ГСЧ — предостаточно.

А ведь король-то голый!

В итоге — пришлось попросту отказаться от использования openssl_random_pseudo_bytes () и перейти на прямое чтение из /dev/urandom. Не самое блестящее решение —, но достаточное в нашем случае.

Поскольку автор не является экспертом в области криптографии и мои выводы могут быть неверны / неполны, а проблема является более чем серьезной, учитывая распространенность рекомендаций по использованию openssl_random_pseudo_bytes (), то я обязательно изучу все комментарии специалистов и возможно исправлю / дополню (или удалю, если в корне не прав) статью. Также, если выводы подтвердятся, необходимо будет внести дополнения в документацию ПХП и предложения по добавлению RAND_seed/RAND_poll и / или их вызовы при старте скрипта в ПХП.

Важно! Apache должен работать в prefork режиме (MPM prefork). Версия ПХП с которой проблема проверялась — 5.5.x, но, предположительно, будет воспроизводиться в любой версии имеющей openssl_random_pseudo_bytes ()

P.S. Я отписался в security@php.net — почти месяц назад. Ни ответа, ни привета. Или не получили. Или проигнорировали. Не знаю.
Так что вывожу статью обратно в онлайн.

© Habrahabr.ru