# hotpcalc Инструмент для работы с токенами **SafeNet eToken PASS** — HOTP (RFC 4226) и TOTP (RFC 6238). Умеет три вещи: 1. **Калькулятор.** По вектору инициализации (seed) и **двум последовательным** значениям, снятым с токена, определяет его текущую позицию (счётчик для HOTP, временной шаг для TOTP) и рассчитывает следующие N значений. 2. **Программный токен.** Хранит seed и счётчик в ini-файле и по команде выдаёт следующее значение — как физический брелок. 3. **Выпуск токенов.** Генерирует новый токен и выгружает файл инициализации в XML-формате SafeNet/Aladdin для импорта на сервер аутентификации. Написан на Go без внешних зависимостей; собирается в один статический бинарник под Linux, macOS и Windows (amd64 и arm64). ## Сборка ```sh make build # локальный бинарник ./hotpcalc make test # тесты, включая контрольные векторы RFC 4226 и RFC 6238 make dist # бинарники под linux/macos/windows, amd64/arm64 в каталоге dist/ ``` Требуется Go 1.26 или новее. Готовые бинарники в `dist/` ни от чего не зависят — достаточно скопировать нужный файл и сделать его исполняемым: ```sh chmod +x hotpcalc-linux-amd64 ./hotpcalc-linux-amd64 -version ``` На macOS файл, скачанный через браузер, помечается карантином; снять метку: ```sh xattr -d com.apple.quarantine hotpcalc-darwin-arm64 ``` В консоли Windows перед запуском может понадобиться переключение на UTF-8, иначе русский текст выведется нечитаемо (в Windows Terminal это не нужно): ``` chcp 65001 hotpcalc-windows-amd64.exe -seed 765D... -otp1 941595 -otp2 057938 ``` ## Команды ``` hotpcalc [флаги] [seed] [код1] [код2] калькулятор (режим по умолчанию) hotpcalc new выпустить новый токен: seed, серийник, запись в ini, выгрузка XML hotpcalc add добавить в ini уже известный токен по его seed hotpcalc import загрузить токены из XML-файла инициализации hotpcalc list список токенов в хранилище hotpcalc show параметры и состояние токена (счётчик не двигается) hotpcalc next выдать следующее значение — «нажатие кнопки» hotpcalc token интерактивный режим: Enter = нажатие кнопки hotpcalc sync найти позицию по двум кодам и записать её в хранилище hotpcalc set изменить параметры токена hotpcalc export выгрузить XML-файл инициализации hotpcalc remove удалить токен из хранилища hotpcalc calc явный вызов калькулятора ``` Справка по любой команде — `hotpcalc <команда> -h`. ## Калькулятор ``` hotpcalc [флаги] [seed] [код1] [код2] ``` Самый короткий вызов — три позиционных аргумента: ```sh ./hotpcalc 765D7268D012090C28172A729BC9CC1AD82B382D 941595 057938 ``` ``` Seed: 765D7268…382D (20 байт, hex) Режим: HOTP, SHA1, 6 разрядов Позиция: код 941595 → счётчик 41, код 057938 → счётчик 42 Следующие значения (10): # Счётчик Код 1 43 937300 2 44 991694 3 45 146545 ... ``` Без аргументов программа спрашивает seed и коды в диалоге: ```sh ./hotpcalc Введите seed (hex или Base32): ... Введите первый код с токена: ... Введите второй (следующий) код: ... ``` ### Флаги | Флаг | Назначение | По умолчанию | |------|------------|--------------| | `-mode` | режим: `hotp` или `totp` | `hotp` | | `-seed` | вектор инициализации | — | | `-format` | формат seed: `auto`, `hex`, `base32` | `auto` | | `-otp1`, `-otp2` | два последовательных значения с токена | — | | `-n` | сколько следующих значений рассчитать | `10` | | `-digits` | разрядность кода (6 или 8) | по длине введённых кодов | | `-alg` | `sha1`, `sha256`, `sha512` | `sha1` | | `-step` | шаг времени TOTP | `30s` | | `-start`, `-span` | диапазон перебора счётчика (HOTP) | `0`, `1000000` | | `-window` | окно перебора вокруг текущего времени (TOTP); понимает `d` и `w` | `90d` | | `-counter` | считать прямо с указанного счётчика/шага, без поиска | — | | `-at` | считать текущим моментом указанное время, RFC 3339 (TOTP) | — | | `-all` | вывести результат для всех найденных совпадений | выкл. | | `-json` | машиночитаемый вывод | выкл. | | `-version` | показать версию | — | Флаги можно ставить и после позиционных аргументов: `./hotpcalc <код1> <код2> -n 20`. ### Примеры Двадцать следующих значений в режиме HOTP: ```sh ./hotpcalc -seed 765D7268D012090C28172A729BC9CC1AD82B382D -otp1 941595 -otp2 057938 -n 20 ``` Восьмиразрядный токен (разрядность определяется автоматически по длине кодов): ```sh ./hotpcalc -seed 765D... -otp1 12345678 -otp2 87654321 ``` TOTP с шагом 60 секунд и seed в Base32: ```sh ./hotpcalc -mode totp -step 60s -seed GEZDGNBVGY3TQOJQ -otp1 123456 -otp2 654321 ``` Расчёт прямо с известного счётчика, без поиска: ```sh ./hotpcalc -seed 765D... -counter 100 -n 5 ``` Токен с большим значением счётчика — расширяем диапазон перебора: ```sh ./hotpcalc -seed 765D... -otp1 941595 -otp2 057938 -span 10000000 ``` Машиночитаемый вывод: ```sh ./hotpcalc -seed 765D... -otp1 941595 -otp2 057938 -n 3 -json ``` ## Программный токен Хранилище — ini-файл, по умолчанию `tokens.ini` рядом с исполняемым файлом. Путь меняется флагом `-store` или переменной окружения `HOTPCALC_STORE`. Выпуск токена: генерируется случайный seed (`crypto/rand`, 20 байт), серийный номер и файл инициализации для сервера. ```sh $ ./hotpcalc new -label "Иванов И.И." -xml ivanov.xml Выпущено токенов: 1 0004003D473F7D (Иванов И.И.) тип HOTP, SHA1, 6 разрядов seed 281CCDCD…84F1 (20 байт) счётчик 0 (значение следующего нажатия) выпущен 2026-09-04 11:23:14 Хранилище: /opt/hotpcalc/tokens.ini XML: ivanov.xml ``` Дальше токен работает как брелок — каждое `next` это одно нажатие кнопки: ```sh $ ./hotpcalc next 0004003D473F7D 521232 ``` Код уходит в stdout, служебная строка (`0004003D473F7D: счётчик 0 → 1`) — в stderr, поэтому команду можно подставлять прямо в скрипты. Флаг `-n` выдаёт несколько значений за раз. Интерактивный режим ближе к настоящему токену: ```sh $ ./hotpcalc token 0004003D473F7D Токен 0004003D473F7D (Иванов И.И.) HOTP, SHA1, 6 разрядов Enter — нажатие кнопки, q — выход. > 448181 (счётчик 0) > 257719 (счётчик 1) > q ``` Остальные команды: ```sh ./hotpcalc list # что вообще есть в хранилище ./hotpcalc show 0004003D473F7D -peek 5 # состояние и будущие коды, счётчик не двигая ./hotpcalc sync 0004003D473F7D 394216 767221 # ресинхронизация по двум кодам ./hotpcalc set 0004003D473F7D -counter 100 # правка вручную ./hotpcalc add -label "Старый токен" 765D7268... # завести токен по известному seed ./hotpcalc remove 0004003D473F7D # удалить ``` Состояние сохраняется **до** вывода кода, а хранилище на время работы блокируется файлом `tokens.ini.lock`, поэтому параллельные вызовы не могут получить один и тот же счётчик. Замок старше минуты считается брошенным и снимается автоматически. ### Формат хранилища ```ini ; hotpcalc — хранилище токенов ; Файл содержит секреты в открытом виде: защищайте его как пароль. ; Одна секция — один токен, имя секции — серийный номер. [0004003D473F7D] type = hotp seed = 281CCDCD4E26ACFD57A09450358FF257FEA484F1 digits = 6 algorithm = sha1 counter = 0 label = Иванов И.И. created = 2026-09-04T11:23:14+03:00 last_used = 2026-09-04T11:23:24+03:00 ``` Файл можно править вручную: комментарии, порядок секций и порядок ключей сохраняются при перезаписи. Для TOTP вместо `counter` пишутся `period` и `last_step`. ## Файл инициализации (XML) `new -xml`, `add -xml` и `export` выгружают токены в родном формате SafeNet/Aladdin — том самом, что поставляется с закупленными eToken PASS. Его импортируют SafeNet Authentication Manager, privacyIDEA, LinOTP и совместимые серверы. ```xml 5 101 09/04/2026 eToken PASS 281CCDCD4E26ACFD57A09450358FF257FEA484F1 0 ``` Заводские поля меняются флагами `-connector-id`, `-product-name`, `-case-model`, `-model`, `-production-date`. **Ограничение формата.** В нём нет полей для разрядности, режима и периода: он описывает шестиразрядный HOTP, а алгоритм импортирующая сторона выводит из длины seed (64 hex-символа — SHA256, иначе SHA1). Если выгружается токен с другими параметрами, программа предупреждает об этом явно: ``` Внимание: формат не хранит разрядность — сервер импортирует токен как 6-разрядный, а не 8-разрядный ``` Выпуск партии токенов одним файлом: ```sh ./hotpcalc new -count 50 -label "Отдел продаж" -xml batch.xml ./hotpcalc export -all -o all.xml # выгрузить всё хранилище ./hotpcalc import postavka.xml # загрузить закупленные токены ``` ## Форматы seed Формат определяется автоматически: * **hex** — как на бланке eToken PASS: 40 шестнадцатеричных символов (20 байт). Пробелы, дефисы и двоеточия игнорируются; * **Base32** — как в Google Authenticator, регистр и padding `=` не важны; * **`otpauth://` URI** — из QR-кода. Параметры `digits`, `algorithm`, `period` и `counter` берутся из URI, если не заданы флагами явно. ## Как работает поиск позиции Токен eToken PASS в режиме HOTP хранит счётчик, который увеличивается при каждом нажатии кнопки. Два подряд снятых значения однозначно задают позицию: программа перебирает счётчики в диапазоне `[-start, -start + -span)` и ищет тот, для которого первый код приходится на счётчик `C`, а второй — на `C+1`. В режиме TOTP перебираются временные шаги вокруг текущего момента в пределах `±-window`, что позволяет заодно измерить расхождение часов токена и этой машины. Если совпадений найдено несколько (вероятность порядка `span / 10^12` — практически исключено), программа предупреждает об этом; полный список выводится с флагом `-all`. Если совпадений нет, стоит проверить seed, разрядность, режим и порядок кодов, а затем расширить диапазон перебора. ## Проверка корректности Реализация проходит контрольные векторы: * RFC 4226, Приложение D — все десять значений HOTP; * RFC 6238, Приложение B — TOTP для SHA1, SHA256 и SHA512. Отдельно проверяется, что программный токен и калькулятор дают одно и то же: коды из `next` сверяются с расчётом по seed из выгруженного XML, а параллельные нажатия не выдают повторов. ```sh make test ``` Сверка с внешним инструментом: ```sh oathtool --hotp -c 0 3132333435363738393031323334353637383930 # 755224 ./hotpcalc -seed 3132333435363738393031323334353637383930 -counter 0 -n 1 ``` ## Безопасность Seed — это секрет, эквивалентный самому токену. Программа никуда его не передаёт: сетевых обращений нет. По умолчанию seed печатается сокращённо (`765D7268…382D`), полностью — только по флагу `-show-seed` и в выводе `-json`. Ini-хранилище и XML-файлы создаются с правами `0600` (на Windows биты прав не действуют — полагайтесь на права каталога). Оба содержат секреты открытым текстом: - удаляйте XML-файл инициализации после импорта на сервер — об этом программа предупреждает сама; - относитесь к `tokens.ini` как к файлу паролей; - шифрование хранилища не реализовано: если это важно, держите файл на зашифрованном разделе. Seed, переданный флагом, попадает в историю командной строки и в список процессов — в чувствительных случаях вводите его в диалоге.