Небезопасный SecureRandom
Уже не вспомнить как, но однажды я наткнулся на тикет в багтрекере Ruby с обсуждением класса SecureRandom
из стандартной библиотеки. Я так увлекся, что провел весь вечер за чтением этого длиннющего треда. Он полон эмоций, страстей, презрения, упреков и негодования. Рекомендую как-нибудь тоже полистать на досуге. В то же время в нем затронули вопрос алгоритмов криптографии и их поддержки в операционных системах. И я не смог удержаться и не описать кратко ход событий.
Но вначале небольшое отступление, чтобы была понятна суть проблемы.
SecureRandom
SecureRandom
позволяет генерировать последовательность случайных байт заданной длины и гарантирует высокий уровень этой случайности. Он предназначен в первую очередь для реализации механизмов безопасности приложения. Например, в Rails он используется для генерации secret key приложения, session id, csrf токенов, UUID‘ов итд.
Приведу примеры из документации:
SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
Фактически SecureRandom
это CSPRNG (cryptographically secure pseudo-random number generator) и является оберткой над уже существующими реализациями.
С седых времен для выбора PRNG в SecureRandom
применялась следующая логика:
- PRNG из OpenSSL, если она доступна в системе
/dev/urandom
- псевдофайл в Unix системах, использует CSPRNG, реализованный в ядре операционной системыCryptGenRandom
- системный вызов WinAPI в Windowsraise NotImplementedError, "No random device"
в противном случае
Именно в этом порядке приоритетов и заключается проблема, описанная в тикете.
CSPRNG и немного теории
Генераторы по настоящему случайных данных HRNG (hardware random number generator) существуют и используются но обладают относительно низкой скоростью генерации. Поэтому генераторы псевдослучайных чисел PRNG (pseudorandom number generator) применяются намного шире. PRNG это алгоритм генерации числового ряда, который обычно инициализируется (сидируется) по настоящему случайными данными из HRNG.
CSPRNG - это PRNG которые обладают свойствами, которые делают их применимыми в криптографии:
- они проходят тесты на статистическую случайность (Next-bit test) - т.е. нельзя предугадать следующий генерируемый бит с вероятностью выше 50% за полиномиальное время
- они устойчивы к атакам даже если известно частично начальное состояние.
Основное свойство CSPRNG - очень трудно (но не невозможно) заранее предугадать генерируемые данные.
CSPRNG делят на следующие типы:
- основанные на шифрах или криптографических хеш-функциях
- основанные на трудно вычислимых математических задачах
- все остальные алгоритмы, не вошедшие в предыдущие категории, например:
- Yarrow - используется в MacOS, в том числе для
/dev/random
- ChaCha20, который используется в OpenBSD, FreeBSD, NetBSD и Linux
- arc4random
- Yarrow - используется в MacOS, в том числе для
CSPRNG стали неотъемлемой частью ядер современных операционных систем. В Unix’ах они доступены как через системные вызовы, специфичные для каждой системы, так и через более-менее универсальный интерфейс - псевдофайлы /dev/random
и /dev/urandom
.
CSPRNG работают по одному и тому же принципу. При старте операционной системы генерируется по настоящему случайные данные (entropy pool) используя HRNG и затем они используется для инициализации CSPRNG (обычно это ChaCha20). Если приложение обращается к PRNG до завершения генерации entropy pool, то возможны два варианта:
- вызов блокируется пока entropy pool не “наполнится” или
- PRNG будет проинициализирован псевдослучайными данными, что конечно же уменьшает случайность.
Итак, шел 2014 год
Некий Corey Csuhta создал тикет и предложил Ruby core team отказаться от использования OpenSSL в SecureRandom
, ибо оно дырявое как решето, а реализация в ядре операционной системы намного надежнее и проще. В ней может разобраться даже ребенок… обычный опытный разработчик на Си, а не то что Ruby core team. Приводили примеры реализации в Linux, реализации в libsodium, реализации функции arc4random в OpenBSD и даже реализацию, прости, Господи, в PHP.
PRNG в OpenSSL печально известен серией багов и уязвимостей (тыц, тыц, тыц и наконец тыц). С одним из них (random fork-safety) даже связан костыль в SecureRandom
. Один из участников обсуждения создал issue в проекте OpenSSL по этому поводу.
Позиция core team была следующей - OpenSSL наше все, хоть и дырявое, а на /dev/urandom
перейти нельзя, потому что в (устаревшей) man странице Linux не рекомендовали использовать /dev/urandom
как CSPRNG. В документации MacOS, FreeBSD и OpenBSD, кстати, такого предупреждения не было. Таким образом, из-за устаревшей документации Linux Ruby не использует PRNG операционной системы как на Unix’ах так и на Windows.
Оказалось, что по поводу этой устаревшей man страницы в Linux был заведен даже баг но заниматься им никто не спешил.
Тем временем шел 2015 год
Как будто в ответ на критику в треде Ruby core team решила принять меры и усилила безопасность SecureRandom
, начав инициализировать PRNG OpenSSL случайными байтами, сгенерированными PRNG операционной системы. На это сообщество отреагировало негативно и в самом коммите на Github и в треде.
Примечательно, но один из Ruby committers (NARUSE, Yui, aka nurse) сделал обертку над библиотечными функциями BSD/Linux arc4random_buf, которая повторяла интерфейс стандартного SecureRandom
но не использовала OpenSSL, и выложил это в виде gem‘а. Еще можно было использовать binding к libsodium (rbnacl), кросплатформенной библиотеке криптографических алгоритмов, или еще одну обертку над libsodium, которая реализовывала интерфейс SecureRandom
.
Немного изменилась и позиция Ruby core team. Они согласились, что реализация PRNG в Linux читабельна, и признали, что /dev/urandom
надежен. Но не хватает только официального подтверждения, что в Linux приложения могут интенсивно использовать /dev/urandom
. Вернее, документация утверждает обратное.
Прозвучала даже мысль вынести SecureRandom
в отдельный gem, чтобы его обновление было независимо от релизов самого Ruby. И если будет найдена уязвимость, то надо будет просто зарелизить новую версию gem‘а.
Привели также результат Diehard теста (набора статистических тестов для измерения качества набора случайных чисел) для SecureRandom
и /dev/urandom
в Debian. Результат ожидаем - 6 непройденных тестов у SecureRandom
и 1 у /dev/urandom
(отчет).
Наступил 2017 год
Наконец-то в Linux обновилась документация (random (4), random (7)). В треде об этом просигналили и через несколько месяцев в SecureRandom
логика выбора PRNG была изменена. Вначале SecureRandom
обращается к PRNG операционной системы и далее уже к OpenSSL, как и предлагалось изначально в тикете.
Новые приоритеты выбора PRNG в SecureRandom :
- CSPRNG реализация операционной системы, один из:
- системный вызов
getrandom(2)
, arc4random(3)
или- системный вызов
CryptGenRandom
- системный вызов
/dev/urandom файл
- OpenSSL -
RAND_bytes(3)
Системный вызов getrandom
в Linux/BSD практически эквивалентен чтению из /dev/urandom
псевдофайла, но имеет ряд дополнительных возможностей. Кроме того, системный вызов более безопасный, так как будет блокировать вызов если entropy pool операционной системы еще не “заполнен”.
Библиотечная функция arc4random
(на самом деле целый набор функций) это еще один PRNG, доступный в Unix’ах. Изначально он возник в BSD системах, был портирован на Linux и теперь доступен там в составе библиотеки libbsd. В начале там использовался ARC4 шифр, который затем постепенно был заменен на ChaCha20.
Это изменение вошло в Ruby 2.5 и стало доступным в декабре 2017 спустя 4 года с создания тикета и начала обсуждения. В списке изменений появился следующий лаконичный пункт:
“SecureRandom now prefers OS-provided sources than OpenSSL. [Bug #9569]”
Эпилог
Несмотря на консервативность и упертось Ruby core team теперь в Ruby есть надежный CSPRNG из коробки, который доступен через SecureRandom.random_bytes
. Осталось только дождаться гемификации и выноса этой библиотеки в независимый gem.
Одновременно с обсуждением шла работа над улучшением CSPRNG в ядре Linux. В 2016 они перешли на новый алгоритм (ChaCha20), который к тому времени уже использовался во всех BSD системах, и было сделано много других изменений, в которые я не углублялся (детали здесь, пример и еще один).
Параллельно с этим в OpenSSL закрыли issue и отрапортовали, что их PRNG переписан с нуля и теперь белый и пушистый. Это изменение стало доступным в 2018 году в версии 1.1.1 (CHANGES):
Grand redesign of the OpenSSL random generator
The default RAND method now utilizes an AES-CTR DRBG according to NIST standard SP 800-90Ar1. The new random generator is essentially a port of the default random generator from the OpenSSL FIPS 2.0 object module. It is a hybrid deterministic random bit generator using an AES-CTR bit stream and which seeds and reseeds itself automatically using trusted system entropy sources.
Ссылки
- Cryptographically secure pseudorandom number generator
- On entropy and randomness
- The plain simple reality of entropy (слайды)
- Falko Strenzke. An Analysis of OpenSSL’s Random Number Generator
- Patrick Lacharme, Andrea Röck, Vincent Strubel, Marion Videau. The Linux Pseudorandom Number Generator Revisited