Как работают коды TOTP

Код TOTP — это 6-значное число, которое приложение-аутентификатор и сервер вычисляют каждый самостоятельно, из одних и тех же двух входных данных: общего секретного ключа и текущего времени, нарезанного на 30-секундные окна. Сам код при настройке никогда не передаётся и не несёт никакой информации о секрете, и всё же обе стороны приходят к одним и тем же цифрам — потому что TOTP не магия и не случайность, а опубликованный алгоритм (RFC 6238), применённый к одним и тем же входным данным. Эта статья проходит вычисление от начала до конца, объясняет, почему «неверный код» — почти всегда проблема часов, и разбирает правила обращения, от которых зависит, останется ли ваш второй фактор вторым фактором. Каждый шаг можно выполнить самому в генераторе TOTP, который вычисляет коды локально в вашем браузере.

Два входа, один код: секрет и часы

Когда вы включаете двухфакторную аутентификацию, сервис генерирует случайный секрет — обычно 20 байт, показанных вам строкой символов Base32 и для удобства закодированных в QR-код. Этот секрет хранится ровно в двух местах: в базе данных сервиса и в вашем аутентификаторе. С этого момента вход в систему — синхронизированное вычисление. Обе стороны берут текущее время Unix, делят его на шаг времени (30 секунд почти у всех сервисов) и подают получившийся счётчик вместе с секретом в хеш-функцию. Тот, кто предъявит совпадающий результат, считается владельцем секрета.

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

Вычисление, шаг за шагом

Счётчик сначала записывается как 8-байтовое целое число в порядке big-endian — кодировка фиксированной ширины, о которой обе стороны договорились. Затем это сообщение пропускается через HMAC с секретом в качестве ключа, с согласованным хешем: SHA-1 по умолчанию, а SHA-256 и SHA-512 определены для сервисов, которые их выберут. HMAC — это конструкция с ключом, а не простой хеш: простое хеширование счётчика позволило бы любому, кто знает алгоритм, воспроизводить коды, тогда как выходы HMAC неподделываемы без секрета и не могут быть обращены, чтобы его восстановить.

Дайджест — 20 байт и больше; поле входа хочет 6 цифр. Мост между ними — динамическое усечение: последний полубайт дайджеста выбирает смещение, четыре байта читаются с этого смещения с замаскированным старшим битом (чтобы избежать неоднозначности знака), а получившееся 31-битное число приводится по модулю 10 в степени числа цифр — миллион для стандартных шести цифр — и дополняется нулями слева. Это и есть код, который вы вводите. Следующее окно пересчитывает с нуля; никакой цепочки между кодами нет, поэтому сфотографированный код через тридцать секунд ничего не стоит.

Когда код отклоняют: обычно дело в часах

Поскольку счётчик выводится из времени, генератор и сервер должны сходиться в том, какое 30-секундное окно сейчас текущее. Большинство серверов смягчают это, принимая также соседние окна — по одному шагу в каждую сторону, — что тихо поглощает небольшие отклонения. За этими пределами каждый код неверен, каким бы правильным ни был секрет, а отказ выглядит в точности как опечатка в цифре: плоский отказ без единой подсказки о том, какой вход разошёлся.

Поэтому первая диагностика внезапно отказавшего аутентификатора — никогда не секрет; это часы устройства, которое генерирует коды. Время, выставленное вручную, умирающая батарейка CMOS, телефон, проведший полёт в неверном режиме часового пояса — любое из этого может увести устройство на минуту и больше. Наш генератор TOTP показывает живую карточку часов и сверяет ваше устройство с серверным временем именно поэтому. Включите автоматические дату и время — и коды почти всегда снова заработают: без сброса, без нового секрета, без обращения в поддержку.

Секрет — это вся игра

Если убрать церемонию, безопасность TOTP сводится к одному факту: у кого есть секрет, у того есть второй фактор — навсегда, отовсюду, и сервис не может отличить его коды от ваших. QR-код, который вы сканируете при настройке, — не картинка кода: это сам секрет плюс метка аккаунта и параметры, оформленные как URI otpauth://. Скриншот этого кода — постоянная копия вашего второго фактора.

Поэтому правила обращения строже, чем для паролей. Пароль проверяется сервером и может храниться в хешированном виде; секрет TOTP должен извлекаться обеими сторонами в исходном виде, поэтому его нельзя растворить хешем, и каждая копия — живая копия. Храните главную копию в аутентификаторе или в зашифрованном менеджере паролей. Никогда не вводите его на сайте, который генерирует коды на своём сервере, — вы вручили незнакомцу постоянный ключ, — и никогда не вставляйте его в чат, как объясняет следующий раздел.

Почему секрет нельзя вставлять в чат

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

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

Что TOTP не останавливает

TOTP побеждает кражу паролей, подстановку учётных данных и утечки баз данных: знать ваш пароль больше недостаточно. Он не побеждает фишинг в реальном времени. Поддельная страница входа, которая передаёт ваши учётные данные настоящему сервису и просит код, может передать код дальше внутри его 30-секундного окна — код доказывает владение секретом, а не то, что вы говорите с подлинным сайтом. Кража сессионного cookie обходит его столь же полно, потому что код проверяется только один раз.

Вот почему пасскеи и аппаратные ключи безопасности вытесняют TOTP на верхнем уровне: они привязывают доказательство к происхождению (origin) сайта, не оставляя ничего вводимого, что ретранслятор мог бы украсть. TOTP остаётся сильной, универсальной базой — намного лучше SMS-кодов, наследующих слабости телефонной сети, — но внедрять его следует с открытыми глазами: защищайте секрет как мастер-ключ, храните резервные коды в отдельном месте, держите часы честными и предпочитайте пасскей везде, где сервис его предлагает.

Источники

Частые вопросы

Может ли кто-то вывести мой секрет из моих кодов?

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

Почему сервисы принимают предыдущий и следующий коды?

Ради допусков. Ваше устройство и сервер редко сойдутся в точной секунде смены окна, а небольшое расхождение часов — обычное дело, поэтому большинство серверов принимает и коды соседних шагов времени. Это расширяет действительное окно с 30 секунд примерно до 90 — сознательный компромисс в пользу удобства: сфотографированный код минуту спустя всё равно ничего не стоит.

Безопасен ли TOTP на SHA-1, если SHA-1 «сломан»?

Да, в этой конструкции. Знаменитые атаки на SHA-1 ломают стойкость к коллизиям, которая важна для подписей и сертификатов. Безопасность HMAC не опирается на стойкость к коллизиям — она опирается на то, что конструкция с ключом ведёт себя как псевдослучайная функция, — и не существует практической атаки, которая восстанавливала бы секрет TOTP или подделывала коды по наблюдаемым выходам. Сервисы всё же могут выбрать SHA-256; генератор на этом сайте поддерживает все три.