Initial commit: hotpcalc CLI
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
# 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, переданный флагом, попадает в историю командной строки и в список процессов —
|
||||
в чувствительных случаях вводите его в диалоге.
|
||||
Reference in New Issue
Block a user