Built-in job token (secrets.GITHUB_TOKEN) returns 401 against the Package Registry API on this Gitea instance even with permissions: packages: write, so use a manual PACKAGE_TOKEN secret instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hotpcalc
Инструмент для работы с токенами SafeNet eToken PASS — HOTP (RFC 4226) и TOTP (RFC 6238). Умеет три вещи:
- Калькулятор. По вектору инициализации (seed) и двум последовательным значениям, снятым с токена, определяет его текущую позицию (счётчик для HOTP, временной шаг для TOTP) и рассчитывает следующие N значений.
- Программный токен. Хранит seed и счётчик в ini-файле и по команде выдаёт следующее значение — как физический брелок.
- Выпуск токенов. Генерирует новый токен и выгружает файл инициализации в XML-формате SafeNet/Aladdin для импорта на сервер аутентификации.
Написан на Go без внешних зависимостей; собирается в один статический бинарник под Linux, macOS и Windows (amd64 и arm64).
Сборка
make build # локальный бинарник ./hotpcalc
make test # тесты, включая контрольные векторы RFC 4226 и RFC 6238
make dist # бинарники под linux/macos/windows, amd64/arm64 в каталоге dist/
Требуется Go 1.26 или новее. Готовые бинарники в dist/ ни от чего не зависят —
достаточно скопировать нужный файл и сделать его исполняемым:
chmod +x hotpcalc-linux-amd64
./hotpcalc-linux-amd64 -version
На macOS файл, скачанный через браузер, помечается карантином; снять метку:
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]
Самый короткий вызов — три позиционных аргумента:
./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 и коды в диалоге:
./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 <seed> <код1> <код2> -n 20.
Примеры
Двадцать следующих значений в режиме HOTP:
./hotpcalc -seed 765D7268D012090C28172A729BC9CC1AD82B382D -otp1 941595 -otp2 057938 -n 20
Восьмиразрядный токен (разрядность определяется автоматически по длине кодов):
./hotpcalc -seed 765D... -otp1 12345678 -otp2 87654321
TOTP с шагом 60 секунд и seed в Base32:
./hotpcalc -mode totp -step 60s -seed GEZDGNBVGY3TQOJQ -otp1 123456 -otp2 654321
Расчёт прямо с известного счётчика, без поиска:
./hotpcalc -seed 765D... -counter 100 -n 5
Токен с большим значением счётчика — расширяем диапазон перебора:
./hotpcalc -seed 765D... -otp1 941595 -otp2 057938 -span 10000000
Машиночитаемый вывод:
./hotpcalc -seed 765D... -otp1 941595 -otp2 057938 -n 3 -json
Программный токен
Хранилище — ini-файл, по умолчанию tokens.ini рядом с исполняемым файлом.
Путь меняется флагом -store или переменной окружения HOTPCALC_STORE.
Выпуск токена: генерируется случайный seed (crypto/rand, 20 байт), серийный номер
и файл инициализации для сервера.
$ ./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 это одно нажатие кнопки:
$ ./hotpcalc next 0004003D473F7D
521232
Код уходит в stdout, служебная строка (0004003D473F7D: счётчик 0 → 1) — в stderr,
поэтому команду можно подставлять прямо в скрипты. Флаг -n выдаёт несколько
значений за раз.
Интерактивный режим ближе к настоящему токену:
$ ./hotpcalc token 0004003D473F7D
Токен 0004003D473F7D (Иванов И.И.)
HOTP, SHA1, 6 разрядов
Enter — нажатие кнопки, q — выход.
> 448181 (счётчик 0)
> 257719 (счётчик 1)
> q
Остальные команды:
./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, поэтому параллельные вызовы не могут получить один и тот же
счётчик. Замок старше минуты считается брошенным и снимается автоматически.
Формат хранилища
; 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 version="1.0" encoding="UTF-8"?>
<Tokens>
<Token serial="0004003D473F7D">
<CaseModel>5</CaseModel>
<Model>101</Model>
<ProductionDate>09/04/2026</ProductionDate>
<ProductName>eToken PASS</ProductName>
<Applications>
<Application ConnectorID="{ab1397d2-ddb6-4705-b66e-9f83f322deb9}">
<Seed>281CCDCD4E26ACFD57A09450358FF257FEA484F1</Seed>
<MovingFactor>0</MovingFactor>
</Application>
</Applications>
</Token>
</Tokens>
Заводские поля меняются флагами -connector-id, -product-name, -case-model,
-model, -production-date.
Ограничение формата. В нём нет полей для разрядности, режима и периода: он описывает шестиразрядный HOTP, а алгоритм импортирующая сторона выводит из длины seed (64 hex-символа — SHA256, иначе SHA1). Если выгружается токен с другими параметрами, программа предупреждает об этом явно:
Внимание: формат не хранит разрядность — сервер импортирует токен как 6-разрядный, а не 8-разрядный
Выпуск партии токенов одним файлом:
./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, а параллельные
нажатия не выдают повторов.
make test
Сверка с внешним инструментом:
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, переданный флагом, попадает в историю командной строки и в список процессов — в чувствительных случаях вводите его в диалоге.