Установить в Chrome

Как это работает

Технический обзор для тех, кто проверяет безопасность. Всё изложенное сверяется с исходным кодом — включая то, что нас не красит: мы это называем, а не опускаем.

Архитектура

Бэкенда нет. Это расширение Chrome на Manifest V3, которое генерирует коды TOTP/HOTP локально с помощью OTPAuth. Мы не держим серверов, не заводим учётных записей, и у нас нет канала, по которому данные пользователя могли бы к нам попасть.

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

Криптография

Защита паролем необязательна и по умолчанию выключена. Когда она включена:

Шифр
AES-256-GCM, случайный 96-битный IV для каждой записи и каждой перезаписи
Выработка ключа
PBKDF2-HMAC-SHA256, 600 000 итераций, случайная 128-битная соль (по рекомендациям OWASP)
Мастер-ключ
256 бит, из crypto.getRandomValues, генерируется один раз; пароль лишь оборачивает его
Отпечатки
HMAC-SHA256 на подключе, выведенном через HKDF; нужны для сведения записей между устройствами
Хранение разблокированного ключа
chrome.storage.session — только в памяти, никогда не пишется на диск, очищается при закрытии браузера, недоступен из content-скриптов
Автоблокировка
При каждом открытии / через 5, 15, 60 минут простоя / до закрытия браузера
Реализация
Только WebCrypto. Ни один примитив не написан вручную, никакая криптобиблиотека не поставляется в сборке.

Почему ключей два уровня

Данные никогда не шифруются ключом, выведенным из пароля, напрямую. Записи шифрует случайный мастер-ключ; вывод PBKDF2 только оборачивает этот мастер-ключ. Отсюда два следствия, и оба важны:

  • Смена пароля переписывает 32 байта вместо перешифрования каждой записи, каждой резервной копии и каждого экспорта. Именно на массовой перезаписи хранилища и теряют данные.
  • 160-битный код восстановления может независимо обернуть тот же мастер-ключ, поэтому забытый пароль не приговор. Код показывается один раз, его нужно ввести обратно для подтверждения, и он меняется после каждого использования. На диск ничего не пишется, пока пользователь его не подтвердил.

Что шифруется, а что нет

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

Говорим прямо, потому что для вашей оценки это существенно:

  • Если защита паролем выключена, аккаунты хранятся незашифрованными и, если включена синхронизация Chrome, реплицируются через собственный аккаунт Google пользователя. К нам они не попадают никогда, но Google их хранит. Синхронизацию можно отключить — это заодно удалит и то, что там уже есть.
  • История использования по сайтам — какой аккаунт применяется на каком домене — хранится локально и, пока собирается, хранилищем не защищена. Включение защиты паролем её удаляет; её также можно отключить отдельно.

Модель угроз

Защищает от

  • Кражи или копирования каталога профиля браузера
  • Вредоносных программ-стилеров, выгружающих данные браузера целиком
  • Физического доступа к оставленному без присмотра компьютеру
  • Попадания записей аккаунтов в Google через синхронизацию Chrome
  • Потери или кражи файла резервной копии (экспорт под паролем)

Не защищает от

  • Вредоносного кода, работающего от имени пользователя при разблокированном хранилище
  • Кейлоггера, перехватывающего пароль в момент ввода
  • Скомпрометированного браузера или операционной системы
  • Взлома самих сервисов, для которых предназначены коды

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

Разрешения

Манифест объявляет ровно два разрешения и ни одного host-разрешения:

storage
Аккаунты и настройки в локальном хранилище, разблокированный ключ в session storage и необязательная копия для синхронизации
activeTab
Читает имя хоста активной вкладки, чтобы подсветить подходящий аккаунт, и делает снимок вкладки, когда пользователь сканирует QR-код с экрана

В расширении нет content-скриптов, нет разрешений tabs, cookies и webRequest и нет web-accessible-ресурсов. Расширение не может читать или изменять содержимое какой бы то ни было страницы.

Камера

Сканирование QR-кода камерой работает на странице расширения, открытой во вкладке, а не во всплывающем окне: popup уничтожается при потере фокуса, а запрос разрешения его как раз отбирает. Для этого не нужно разрешение в манифесте: используется штатный запрос камеры браузера, который выдаётся на origin расширения и отзывается в настройках сайта, и он никогда не появляется, пока пользователь не откроет сканер. Видео декодируется локально, никуда не передаётся и не сохраняется.

Сетевые запросы

При обычной работе расширение не делает никаких автоматических запросов, а Manifest V3 запрещает удалённый код. Все соединения, которые вообще возможны:

ХостКогдаПередаёт данные пользователя?
worldtimeapi.org
timeapi.io
Проверка расхождения часов, кэшируется, при ошибке молча пропускается. При сбитых часах TOTP ломается.Нет
authenticator.shСтраница приветствия при установке; страница обратной связи при удалении или оценке.Нет (обычные логи веб-сервера)

Ни шрифты, ни скрипты, ни стили, ни изображения не загружаются со сторонних хостов. Всё, что нужно для отрисовки интерфейса, лежит внутри пакета.

Как проверить то, что мы публикуем

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

  1. Скачайте опубликованный .crx из Chrome Web Store и распакуйте его
  2. Переключитесь на соответствующий git-тег
  3. Выполните npm ci && npm run build на Node 20 LTS
  4. Сравните получившийся dist/ с распакованным пакетом

SHA-256 для каждого файла в собранном dist/ публикуется с каждым релизом на GitHub в виде SHA256SUMS-v<version>.txt, в формате sha256sum — так что шаг 4 сводится к одному запуску sha256sum -c. Расхождения должны ограничиваться порядком файлов внутри архивов и пробелами в минифицированном выводе на разных патч-версиях Node.

Зависимостей в рантайме намеренно мало — OTPAuth, React, jsQR, Zustand, Framer Motion и Lucide — и все они зафиксированы закоммиченным lock-файлом. SBOM в формате CycloneDX предоставляем по запросу.

Смотреть исходный код

Чего у нас нет

Мы небольшой независимый разработчик. Если что-то из перечисленного для вашего процесса обязательно, лучше узнать об этом сейчас, а не на третьей неделе оценки:

  • Нет сертификации SOC 2, ISO 27001 или аналогичной
  • Независимый пентест до сих пор не заказывался
  • Нет платной программы bug bounty

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

Как с нами связаться

Сообщения об уязвимостях и вопросы по безопасности: security@authenticator.sh. Политика раскрытия, границы применимости, сроки ответа и условия safe harbour — на странице политики безопасности; что хранится и что покидает устройство — в политике конфиденциальности.

Мы готовы заполнить письменную анкету оценки поставщика или разобрать код вместе с вашей командой безопасности.

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