Смоделируем ситуацию: внезапно бот проходит вашу капчу, потому что заранее знает, какие случайные числа будут сгенерированы. Если рандом предсказуем, то защита становится иллюзией. Чтобы такая ситуация была невозможна, мы используем различные источники рандома. 

Наверняка многие знают про броуновское движение и прочие природные рандомайзеры. Два года назад у меня родилась идея, которую коллеги поначалу приняли за шутку. Рыбы, пузыри, каустики на дне, колышущиеся водоросли — каждую секунду картинка меняется так, как не предскажет ни одна модель. И тут я понял, что можно забрать весь этот хаос в продакшн.

Сегодня аквариумная энтропия, которую создают наши рыбки: Неоны, Данио‑Рерио и Тетры Керри — поступает в препрод Yandex Key Management Service (KMS).

Привет! Меня зовут Дмитрий Вилькер, я разработчик сервисов криптографии в Yandex Cloud, а последние два года ещё и человек, который на работе всерьёз обсуждает корм для рыб и качество случайных чисел, причём в одном и том же проекте. В этой статье расскажу, как так вышло: зачем криптосервисам энтропия, как кадры из аквариума доезжают до KMS и что мы планируем дальше.

Зачем вообще нужна энтропия и откуда она берётся

Энтропия в практическом смысле — это случайные числа. На них держатся все наши криптосервисы:

  • Yandex Certificate Manager генерирует из них приватные ключи сертификатов.

  • Yandex Lockbox — создаваемые секреты.

  • KMS использует их при создании ключей и при шифровании.

Если вдруг «случайные» числа удаётся предсказать, всё это компрометируется: можно найти приватный ключ чужого сертификата, подобрать сгенерированный кем‑то пароль, дешифровать данные. Поэтому мы всегда в поиске источников настоящих, непредсказуемых случайных чисел. В данной ситуации чем больше, тем лучше.

Стандартный поставщик случайности — операционная система: ядро собирает энтропию из слабопредсказуемых событий вроде времени прерываний и операций ввода‑вывода. Ещё есть аппаратные генераторы в процессорах. Уже имеющихся у нас источников было достаточно, но хотелось сделать ещё лучше.

Почти два года назад я завёл тикет‑шутку: давайте генерировать рандом из картинки аквариума. Рыбы, пузыри и растения меняют изображение постоянно и непредсказуемо — почему бы не отдать эту непредсказуемость криптосервисам? Коллеги посмеялись. А потом на тикет пришёл отклик, и шутка поехала в работу.

Тут выяснилось, что криптографическая часть проекта — не самая сложная. Сложная часть плавает и просит есть.

50 литров, самодельная кормушка и потоп 

Обслуживание аквариума.

Мы поставили в кабинете аквариум на 50 литров и стали учиться его обслуживать. С ним многое не получалось: скакал pH воды, рыбы чувствовали себя то хорошо, то плохо, непонятно было, сколько точно нужно корма и света, чтобы не развивались бактерии и водоросли — маленький объём тяжело стабилизировать. Из этой борьбы с 50 литрами мы вынесли главный аквариумный вывод — надо просто нанять профи. Сейчас за рыбами следят профессиональные аквариумисты, а мы занимаемся тем, что умеем: софтом и железом.

Интерфейс, с помощью которого мы наблюдаем за рыбками и следим за графиком кормления

Софт появился быстро: мы написали свой интерфейс, в котором можно наблюдать за рыбками и кормить их. Когда‑то я работал fullstack‑программистом, поэтому сделать простой веб‑интерфейс было довольно просто. Выбрал самые примитивные инструменты для веб‑разработки: HTML + CSS + JS (ванильный). С точки зрения разработки самой большой трудностью было создать и защитить в службе безопасности схему доставки данных. Сервисы‑то в дата‑центрах, а аквариум у нас в кабинете. Схему, где сервис получает что‑то для своей работы из офисной сети, никто пока не делал.

Видеопоток в интерфейс передается в формате Motion JPEG: кадр за кадром передаются в виде JPEG‑ов. Это создает дополнительный трафик, но зато картинка передается почти без задержки и не тратит ресурсы слабого процессора на сжатие.

Автокормушка
Автокормушка

С железом вышло дольше, так как автокормушку решили собирать сами. Спойлер: версия за версией она находила новые способы не работать. Кормить рыб вручную не получалось по трём причинам. Выходные, отпуска и просто забывчивость — кто‑нибудь обязательно пропустит организацию пира для рыб. Готовой кормушки, которая отсыпает полщепотки, на маркетплейсах я не нашёл: все автоматические кормушки сыпали больше, чем мне посоветовали в аквариумном магазине. А ещё мне нравится мастерить. 

Из доступных технологий у меня были 3D‑печать и лазерная резка акрила. Выбрал резку: получается дешевле, быстрее и результат предсказуемее. Конечно, продумывать схемы можно очень долго: у меня на это уходило от одного дня до месяца. На физическую реализацию у меня уходило примерно 2 дня и 1500 рублей. Один вечер на моделирование, 1–2 дня на изготовление, вечер на сборку, а дальше можно бесконечно наблюдать, что получилось.

Сколько стоит итерация разработки кормушки?
  1. Привод Feetech FT90B — около 800 рублей. Для проекта я нашел такой бесплатно у себя в ящике. Пару раз брал более дешевые аналоги, но они быстро выходили из строя.

  2. Лазерная резка вместе с материалом — 277 рублей на Figuro. Не уверен, что могу указывать конкретный сервис, но он уникальный и искренне нравится моему внутреннему мастеру: быстро, без менеджеров и минимальной стоимости.

  3. Arduino Nano — 160–350 рублей в зависимости от маркетплейса. У меня тоже был в ящике.

Финальный чертёж автокормушки
Финальный чертёж автокормушки

Кормушка устроена просто: три листа акрила стопкой, верхний и нижний неподвижны, а средний двигает привод. В среднем листе отверстие, в которое набирается ровно одна порция корма. Привод сдвигает лист — рыбы получают свою порцию.

За всё время проектирования и проб у меня было 5 попыток. 

Чертёж первой кормушки
Чертёж первой кормушки

Первую кормушку я сделал на два вида корма. Второй вид не понадобился, а потом захотелось ещё и поменять крепление. 

Чертёж второй кормушки
Чертёж второй кормушки

Дальше вылезла проблема: иногда пузыри лопались на поверхности воды и брызги попадали внутрь механизма. Корм намокал, разбухал и застревал. 

Чертёж третьей кормушки
Чертёж третьей кормушки

Раз влага прилетает снизу, я решил заносить механизм над отверстием в крышке только на время кормления. Не взлетело: конструкция получилась хлипкой и шаткой, её вело, и нужны были подшипники. Поэтому такую конструкцию я отложил и пошёл искать другое решение. 

Чертёж четвертой кормушки
Чертёж четвертой кормушки

Четвёртый вариант был уже более жизнеспособен: он использовал тот же принцип, но в упрощённом формате. Механизм не перекидывался, а отъезжал. Идея была перспективной, но прототип показал два недочёта — бегунок ходил туго, а рычаг свисал, потому что плотно посадить его на вал привода не вышло. 

Финальная кормушка
Финальная кормушка

Пятая итерация оказалась победной. Я устранил недочёты четвёртого варианта: изменил форму бегунка и дал рычагу вторую опору — теперь он держится и на приводе, и на корпусе. 

Финальная версия работает без сбоев уже полтора года. Осталась одна загадка: иногда рядом с кормушкой обнаруживается немного корма — откуда он там берётся, я до сих пор не понял. Управляется кормушка через Arduino, которая по USB‑Serial принимает команды и выполняет их. А команды посылает зомбик, но про него чуть дальше.

Кормим мы рыбок каждый день в 13:30 — в это время начинается стендап, так что кормушка заодно работает будильником для команды.

Один инцидент всё‑таки случился. Аквариуму нужно регулярно сливать часть воды, чтобы залить новую (и «пропылесосить» дно). Да, тут автоматизировать не получится. Я решил попробовать провести эту процедуру с помощью маленького насоса, который использовал для залива воды. Однажды мотор сгорел — вода потекла обратно под действием силы тяжести, переполнила аквариум и полилась на пол. Спасло то, что у нас в рабочих задачах выдался перерыв и мы оперативно провели операцию по спасению рыб. 

Итак: поставили и настроили камеру, получили ОК от Security, написали софт для сбора энтропии, подняли инфраструктуру. И запустили доставку аквариумной энтропии в препрод KMS.

Как устроено: от кадра до KMS

Архитектура

Зомбики в Яндексе — это обычные компьютеры, которые крутят графики в офисе или рекламу на кофепоинтах. Наш зомбик ничего не показывает: он обрабатывает изображение с камеры и отправляет его в хаб энтропии, а заодно управляет кормушкой.

Хаб энтропии — сервис, который накапливает случайные числа и раздаёт их криптосервисам по запросу. KMS, Certificate Manager, Lockbox забирают числа оттуда. 

Может показаться, что хаб — лишнее усложнение, поэтому расскажу, почему мы его добавили. По соображениям безопасности сервисы облака не имеют сетевого доступа к зомбику, который стоит в офисе. У зомбика доступ к KMS есть, но и на этом пути хватает сложностей: криптосервисов много, каждый из них автомасштабируемый, и определять актуальные хосты непросто. К тому же в каждом криптосервисе пришлось бы реализовывать приём энтропии и получать апрувы на новый открытый порт к хостам мимо балансера, и это далеко не весь список. Хаб снимает всё это разом.

Помимо этого, для дополнительной безопасности наши криптосервисы используют единый модуль получения случайных чисел на базе алгоритма Fortuna. В этот модуль мы добавили ещё один источник, который периодически пополняет локальный буфер случайных чисел из хаба энтропии.

Как именно собирается энтропия

Демон на зомбике получает с камеры кадры 1280 × 720 точек в формате YCrCb 4:2:0. Кадр в памяти — это три массива байтов: яркость и два цветоразностных компонента. Каждый массив разбивается на 720 кусочков. Демон берёт первые кусочки из каждого массива, склеивает их, хеширует SHA-512 и добавляет результат в буфер случайных чисел. Причём хешируется не только текущая склейка, а все предыдущие плюс текущая: каждая следующая порция энтропии зависит от всей накопленной истории кадров. Процедура повторяется для вторых кусочков из каждого массива, третьих и так далее — а затем демон берёт следующий кадр.

Теперь арифметика. Каждая точка изображения в этом формате занимает 12 бит: 8 бит яркости и 4 бита цвета. В строке 1280 точек, то есть 1280 × 12 / 8 = 1920 байт. С каждых 1920 байт картинки мы получаем 64 байта энтропии — размер хеша SHA-512. Или один бит энтропии на каждые 30 бит картинки.

Забавная деталь: аквариумная камера работает не постоянно, а только когда нужна новая порция энтропии. Когда она включается, на ней загорается светодиод. Рыбки его видят, подплывают полюбопытствовать — и своим движением улучшают получаемую энтропию.

А что, если рыбы замрут?

Каждый кадр всё равно сильно отличается от предыдущего: в него попадают пузыри, колышущиеся растения, каустики. Само изображение с матрицы тоже шумит, даже если снимать чёрный квадрат.

Вот разница двух кадров:

Отображение неизменных частей кадра
Отображение неизменных частей кадра

Розовым помечены участки, которые не изменились. Здесь сравниваются jpeg‑кадры — для энтропии берётся несжатая картинка, там розового ещё меньше

Главная страховка — Fortuna. Алгоритм копит события от нескольких независимых источников и не требует доверять ни одному из них: предсказуемый или скомпрометированный источник не портит общий пул, пока остальные дают настоящую случайность. Даже если наша система целиком даст сбой и в сервис пойдут статичные данные — скажем, одни нули, — ничего страшного не случится. Аквариумный источник может улучшить общую энтропию, а навредить не может.

Качество результата мы проверяем тестами в динамике: да, сырые данные непредсказуемы, но верить на слово в криптографии не принято. Стандартные тесты пройдены: числа статистически случайны, и поток не сжимается архиваторами. Тест сжатием — простая и наглядная проверка: архиватор ищет в данных закономерности, и если сжать поток не удалось, значит, явной избыточности в нём нет. Сейчас мы допрогоняем максимальные статистические тесты (включая пак DieHarder).

Что дальше

У Cloudflare есть похожий проект — Lavarand. Там вместо рыб и пузырей источником хаоса работают лавовые лампы, а в остальном принцип примерно тот же. Принципиальная разница одна: лавовая лампа не подплывает к камере посмотреть, что там загорелось.

Дальше хочется доставлять энтропию в прод — и славы! Для этого нужен аквариум побольше и больше хаоса в нём: живые кораллы, больше рыб, динамическое освещение. Камера тоже нужна получше. Всё это даст больше энтропии, а сам проект сделает эффектнее.

И прятать аквариум в кабинете больше не хочется. Лучше поставить его на видное место, чтобы было не только полезно, но и приятно посмотреть.

Если вам понравилась эта история, можете заглянуть в наш канал, там ещё больше историй и картинок. А пока рыбы трудятся над препродом, вопрос к вам: есть ли у вас в офисе кандидат в генераторы случайных чисел?

Комментарии (7)


  1. kuza2000
    28.08.2026 07:11

    Для развлечения неплохо)

    Но, как мне кажется, матрица резисторов с усилителями даст не худший результат. Там тепловой шум, вплоть до квантовых эффектов. Скорее всего, это будет дешевле, и кормить не нужно)


    1. koreychenko
      28.08.2026 07:11

      Ну, во-первых, это красиво :-)


      1. akuli
        28.08.2026 07:11

        Такая красота требует еженедельной подмены воды, сифонки грунта и борьбы с водорослями. Что-то мне подсказывает что половина отдела уже ненавидит эту банку тихой ненавистью)


  1. ionicman
    28.08.2026 07:11

    Типичный Яндекс - все переусложнено, обязательно свой велосипед (а автоматических кормушек, уже отлаженных на thingiverse и им подобных очень много) и обязательно котики (роль которых здесь исполнили рыбки).

    При этом микросхемы гсч QRNG / TRNG (оптический/электронный шум) полно на рынке и они именно для этого.


  1. jbenderov
    28.08.2026 07:11

    Про арифметику в разделе о сборе: «с каждых 1920 байт картинки получаем 64 байта энтропии» — это размер хеша, а не количество энтропии. SHA-512 энтропию не создаёт, он её только перемешивает: на выходе её не может быть больше, чем было на входе. Если в куске кадра было двадцать бит непредсказуемости, то и в 512-битном хеше будет двадцать бит, просто размазанных по всей длине. Отсюда и «один бит энтропии на каждые 30 бит картинки» — это соотношение объёмов, а не оценка источника.

    Второй момент — про проверку. Тест сжатием и статистические паки, применённые после хеширования, про источник не говорят ничего. Проверяется это за пару минут: берём счётчик 0, 1, 2, … — энтропия ровно ноль — и прогоняем через SHA-512 с накоплением, как у вас. Поток не сжимается, распределение байтов ровное, статистические тесты проходят. Хеш маскирует любую предсказуемость входа, поэтому по его выходу аквариум от счётчика не отличить в принципе.

    Как это принято делать: NIST SP 800-90B требует оценивать min-entropy на сыром выходе шумового источника, до всякого кондиционирования, и именно min-entropy, а не Шеннона — интересен наихудший случай, а не средний. Для камеры это значит считать по несжатым сэмплам и с поправкой на то, что соседние пиксели и соседние кадры сильно скоррелированы: статичный фон, автоэкспозиция и внутрикамерный шумодав съедают заметную часть того, что на глаз выглядит хаосом.

    Про Fortuna всё написано верно, и вывод «навредить не может» тоже верен — слабый источник в пуле действительно не портит остальные. Вопрос только в том, сколько бит аквариум добавляет на самом деле, и ответ даёт оценка входа, а не тесты выхода.


  1. akuli
    28.08.2026 07:11

    Представляю лицо админа дата-центра, которому прилетел тикет на открытие портов из офисного сегмента от камеры с рыбами до боевого кластера управления ключами)


  1. Boomka
    28.08.2026 07:11

    Про лава-лампы Cloudflare никто ещё не написал? Ладно, буду первый