354 lines
16 KiB
Markdown
354 lines
16 KiB
Markdown
# 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 <seed> <код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
|
||
<?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-разрядный
|
||
```
|
||
|
||
Выпуск партии токенов одним файлом:
|
||
|
||
```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, переданный флагом, попадает в историю командной строки и в список процессов —
|
||
в чувствительных случаях вводите его в диалоге.
|