Перейти к содержанию

Небольшой фикс безопасности

Недавно в разговоре мне ненавязчиво намекнули: в нашей базе данных хранятся чувствительные данные пользователей. Telegram ID — тот самый, который мы пишем в базу при регистрации. Мы не запрашиваем его явно - но вы всегда должны помнить, что неявно передаете свой Telegram ID каждому, с кем вступаете в диалог, и каждому сообществу или группе, в которые вы вступаете. Даже если у вас установлен крыж “Скрывать мой Telegram ID” в настройках учетной записи телеграм.

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

Что изменилось #

Раньше в записи каждого пользователя лежало поле telegram_id — обычное целое число. Теперь его нет. Вместо него — два новых поля:

  • hash_telegram_id — необратимый хеш (HMAC-SHA256). По нему система ищет пользователя при каждом обращении к боту.
  • encrypted_telegram_id — зашифрованный Telegram ID (симметричное шифрование Fernet). Используется там, где нужно отправить пользователю сообщение.

То же самое — в системе обращений: поля telegram_id и admin_telegram_id в тикетах заменены на зашифрованные версии. Кроме этого, поменялась механика именования триал-устройств: раньше имя устройства начиналось с Telegram ID пользователя, теперь — с хеш-префикса.

Где оказалось сложнее #

Поначалу задача казалась простой: берешь telegram_id, оборачиваешь его в хеш, подменяешь — и делу край. Но подумав немного над этим, я понял, что все не так просто. На проекте есть два логических слоя, в контексте которых используется Telegram ID.

Первый слой — идентификация и управление. Кто ты такой? Какая у тебя роль? Какие у тебя устройства? Здесь достаточно хеша. Хеш необратим — даже зная его, Telegram ID из него не достать. Задача решена.

Второй слой — живая коммуникация. Уведомление об одобрении регистрации, ответ администратора в тикете, рассылка. Здесь бот отправляет сообщение напрямую через Telegram API — а API принимает только настоящий числовой ID. Хеш тут не поможет никак.

Вот здесь и возникает проблема. Использовать хеш вместо ID при отправке невозможно по определению. Значит, нужно либо хранить настоящий ID (но уже не в открытом виде), либо как-то получать его в момент отправки — не из базы.

Временное решение #

Я выбрал первый вариант как прагматичный: зашифровать ID симметричным ключом. Теперь в базе лежит не Telegram ID, а непрозрачный токен. Бот в момент отправки токен расшифровывает — и работает как раньше.

Это не идеально. Ключ шифрования хранится в окружении бота — то есть, чтобы добраться до реальных ID, нужно скомпрометировать сам сервер. Это куда сложнее, чем просто прочитать JSON-файл из утекшего репозитория, — но всё же не «невозможно».

Я рассматриваю это как приемлимое временное решение.

Куда это движется #

Если читали «Условия входа», то помните: следующий большой этап — распределенное хранилище данных. На этом этапе я буду полностью переписывать бота. И вот тогда, скорее всего, появится окончательное решение проблемы.

Идея такая: бот работает с участниками Telegram-группы или канала. Когда нужно отправить сообщение пользователю — бот перебирает участников, считает хеш для каждого и ищет совпадение. Нашел — отправил. В хранилище при этом хранится только хеш. Никаких зашифрованных ID, никаких ключей дешифрования. Чувствительные данные хранятся in-memory.

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


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