ARMANOS

Двухфакторная защита

Одноразовые коды внутри профиля

У профиля может лежать ключ двухфакторной защиты того аккаунта, в который он заходит. Программа сама превращает ключ в текущий код, в том же окне и без телефона.
RFC 6238 поверх RFC 4226
Стандарт
6 знаков, шаг 30 секунд
Код
SHA1, SHA256, SHA512
Алгоритмы
На вашей машине, без сети
Где считается

Телефон в руке не тянет пятьдесят аккаунтов

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

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

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

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

  • Второе устройство не нужно

    Код появляется в том же окне, где живёт профиль.

  • Не надо искать нужную запись

    Ключ принадлежит профилю, сопоставлять по названию нечего.

  • Не надо спешно перепечатывать

    Код копируется кнопкой, а в форму его может вписать сценарий.

  • В сеть ничего не уходит

    Код считается из ключа прямо на этом компьютере.

Ключ ложится рядом с тем аккаунтом, которому принадлежит

Откройте меню на карточке профиля и выберите «Одноразовый код». Если ключа ещё нет, окно попросит его вместо того, чтобы показывать цифры.

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

Из строки программа читает издателя, имя аккаунта, число знаков, шаг и алгоритм. У голого ключа ничего этого нет, и тогда берутся привычные значения. Если у сайта настройки необычные, вставляйте именно строку.

Ключ, из которого коды не получаются, отвергается сразу и на диск не ложится. Неверный код это неудачный вход, а несколько неудачных входов подряд запирают аккаунт. Строка hotp:// считает входы, а не секунды, и её тоже не принимают.

  1. 1

    Откройте меню профиля

    «Одноразовый код» стоит в том же меню, что «Отпечаток» и «Данные для входа».

  2. 2

    Вставьте ключ

    JBSWY3DPEHPK3PXP или otpauth://totp/... Пробелы, дефисы, нижний регистр и знаки = не мешают.

  3. 3

    Сохраните

    Всё, что не разбирается как base32, возвращается ошибкой, и на диск ничего не пишется.

  4. 4

    Посмотрите на карточку

    На профиле появляется отметка 2FA: сразу видно, каким аккаунтам телефон уже не нужен.

Код считается по часам, и считается он у вас на машине

Расчёт идёт по RFC 6238 поверх RFC 4226. Текущее время делится на шаг, получается счётчик. Счётчик прогоняется через HMAC с вашим ключом, и знаки вырезаются из полученного отпечатка.

Счётчик пишется двумя половинами по 32 бита. Одной 32-битной записи хватает лишь до примерно четырёх миллиардов, дальше код молча становится другим, поэтому проверка закрепляет этот случай эталоном на t=20000000000.

Никуда ничего не отправляется. Ключ остаётся в файле, отпечаток считается в самой программе, и вместе с кодом возвращается остаток текущего окна. Поэтому код получается и при полностью отключённой сети.

НастройкаПо умолчаниюЧто принимается
Число знаков66, 7 или 8, если так сказано в строке otpauth
Шаг30 секундот 1 до 300 секунд из той же строки
АлгоритмHMAC-SHA1SHA1, SHA256 и SHA512
Вид ключаbase32пробелы, дефисы, нижний регистр и знаки =

В окне видно и код, и сколько ему осталось жить

Код стоит крупно посередине и разбит по три знака. «482 913» глазами переносится в форму легче, чем «482913», а кнопка копирования кладёт его в буфер обмена уже без пробела.

Полоска под кодом убывает вместе с окном, значение обновляется раз в секунду. Последние пять секунд отмечены отдельно, чтобы никто не начинал печатать код, который вот-вот сменится.

Строка выше называет издателя и имя аккаунта, прочитанные из строки otpauth. Именно она подтверждает, что код относится к нужному аккаунту, когда профиль один из пятидесяти.

Кнопка «Заменить ключ» ставит новый: так делают после повторной привязки аккаунта. «Убрать» сначала спрашивает, и только потом профиль перестаёт выдавать коды.

Скрипт спрашивает код в ту секунду, когда он нужен

В программе есть местный интерфейс HTTP для программ на этом же компьютере. Он выключен, пока его не включат, слушает только 127.0.0.1, и каждый вызов несёт токен.

Вызов GET /api/v1/profile/code принимает profileId и отвечает текущим кодом и остатком секунд. Ради этого он и нужен: скрипту, который входит в аккаунт, второй шаг требуется ровно в тот миг, когда форма его просит, а не за полминуты до.

Запросы, похожие на пришедшие со страницы, отклоняются. Заголовок Origin, значение Sec-Fetch-Site, отличное от none, или чужой Host обрывают вызов. Открытая вами страница читать это не должна ни при каких условиях.

ИИ-помощник ходит по тому же адресу через встроенный сервер MCP, средством get_one_time_code. Там тот же токен и те же отказы.

Вызов
GET /api/v1/profile/code?profileId=p-42
Токен
параметр token, заголовок x-api-token или Authorization: Bearer
Ответ
{ "code": 0, "data": { "code": "482913", "secondsRemaining": 17, "issuer": "Shop" } }
У профиля нет ключа
code -1, msg no_2fa_key
Токена нет или он не тот
HTTP 401
Запрос пришёл со страницы
Отказ, каким бы ни был токен

Ноль это состояние ответа, общее для всех вызовов. Сами шесть знаков лежат в data.code.

Экран местного интерфейса в ARMANOS: переключатель включён и помечен как работающий, адрес http://127.0.0.1:50327, токен доступа с кнопкой копирования, ниже карточка сервера MCP.
Интерфейс выключен, пока его не включат. Токен с этого экрана скрипт шлёт при каждом вызове, в том числе при запросе кода.

Сценарий вписывает код сам

В сборщике сценариев любое текстовое поле принимает {{profile.totp}}. Поставьте это в шаг ввода, нацеленный на поле кода, и сценарий заполнит его наравне с остальной формой.

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

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

Шаг ставит курсор в поле, очищает его, вводит значение и подаёт события input и change, чтобы форма входа, следящая за настоящим набором, всё равно откликнулась.

Ключ остаётся на этом компьютере

Ключ лежит в profiles.json в папке данных программы, рядом с остальными настройками профиля. Запись неделимая: сперва соседний файл, потом доведение до диска, потом переименование. После обрыва питания на месте оказывается либо целый старый файл, либо целый новый.

Файл пишется с правами «только хозяин», а файлы, оставшиеся открытыми от прежних версий, закрываются при запуске. Эти права уезжают вместе с файлом в резервную копию или в облачную папку.

На Windows такие права меняют лишь признак «только для чтения» и не значат там ничего. Защищает файл сама папка пользователя, куда другие учётные записи без прав администратора не заходят.

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

На диске ключ не зашифрован

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

Пятьдесят ключей приезжают из таблицы

Ввоз из таблицы узнаёт столбец с ключом по названию. Подходят 2fa, totp, otp, authenticator, secretkey и русские написания, так что готовую таблицу обычно переименовывать не нужно.

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

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

Отдельного действия «раздать ключ уже готовым профилям» нет. Ключи попадают в программу либо по одному в окне профиля, либо столбцом при создании профилей.

Чего это не делает

  • Это не хранилище паролей. Один ключ на профиль, без общего сейфа, без поиска по ключам и без места для запасных кодов, которые выдаёт сайт.
  • На диске ключ не шифруется. На macOS и Linux его закрывают права на файл, на Windows только папка пользователя.
  • Потерянный ключ потерян. На сервер он не уходит, и восстановить его по выданным кодам программа не может.
  • Картинки не читаются. Вы вставляете текст ключа или строку otpauth://, а снимок QR или экран телефона программа не разбирает.
  • Код идёт по часам этого компьютера. Если часы уехали, сайт код не примет, и программа об этой причине не предупредит.

Как проверить

За каждым утверждением выше стоит файл, а у большинства ещё и стенд, который падает, когда утверждение перестаёт быть правдой.

Коды сходятся с эталонными векторами RFC 6238, включая счётчик за пределами 32 бит
apps/desktop/src/lib/totp.js · apps/desktop/test/totp.js
Файл с ключом пишется правами «только хозяин», а оставшиеся открытыми файлы закрываются при запуске
apps/desktop/src/lib/secureFile.js · apps/desktop/test/secrets-on-disk.js
Код отдаётся только по токену и никогда веб-странице
apps/desktop/src/lib/local-api.js · apps/desktop/test/local-api-gate.js
Сценарий считает код в момент подстановки
apps/desktop/src/main/main.js · apps/web/test/страница-возможностей.js
Восстановленный профиль сохраняет число знаков, шаг и алгоритм
apps/desktop/src/main/main.js · apps/desktop/test/config-import-2fa.js

Пусть ключ лежит там, где живёт аккаунт

Поставьте ARMANOS, добавьте ключ одному профилю и увидите код в том же окне.