Приложения для двухфакторной аутентификации, такие как Google Authenticator, периодически генерируют одноразовые коды, которые нужно вводить вместе с паролем для дополнительной защиты аккаунта. Такая схема называется TOTP (Time-based One-Time Password Algorithm).
Протокол TOTP:
- Клиент получает уникальный секрет для своего аккаунта
- На основе секрета и текущего времени приложение периодически генерирует коды
- Клиент вводит код для входа в аккаунт
Получение секрета
Обычно для получения секрета нужно отсканировать QR-код. Он содержит строку видаotpauth://totp/Label?Parameters
Метка
Label
– метка, которая используется для определения того, с какой учётной записью связан секрет. Обычно содержит имя учётной записи и название сервиса. Отображается в приложении, чтобы не перепутать, куда вводить код.
Параметры
Parameters
– параметры аутентификации:
Secret
Секрет. Значение закодировано в Base32 без паддинга. Кодировка Base32 содержит только печатные символы: латинские буквы в верхнем регистре и цифры. Благодаря этому секрет можно ввести вручную, если сканирование QR-кода недоступно.
Issuer
Содержит название сервиса, к которому относится аккаунт. Название должно совпадать с названием сервиса в Label
. По стандарту рекомендуется указывать название сервиса и в Issuer
и в Label
, хотя и получается некоторая избыточность.
Algorithm
Определяет хеш-функцию, которая используется при генерации кодов. По умолчанию используется SHA-1.
Digits
Устанавливает количество цифр в одноразовом коде. По умолчанию 6.
Period
Период в секундах, в течение которого действителен код. По умолчанию 30 секунд. Используется отрезок времени, а не точное значение секунда в секунду по двум причинам:
- клиенту нужно некоторое нужно время, чтобы скопировать и ввести код
- время на сервере сервиса и на смартфоне клиента может немного отличаться
Пример:
otpauth://totp/Blog:seregablog?secret=ONSXEZLHMFRGY33HGQZDIMQK&issuer=Blog
Blog
– название сервисаseregablog
– имя аккаунта для входа в сервисONSXEZLHMFRGY33HGQZDIMQK
– секрет
∎
Алгоритм генерации кодов
Коды генерируется на основе секрета и текущего времени с помощью схемы HMAC.
# initial_secret - полученный секрет
# period - период жизни кода
# digits - длина кода
def generateTotp(initial_secret, period, digits):
# декодируем секрет Base32
secret = BASE32_DECODE(secret)
# получаем текущее время и рассчитываем период жизни кода
current_time = CURRENT_UNIX_TIME() / period
# Вычисляем HMAC. Используемая хеш-функция выбирается при начальной настройке
hash = HMAC(secret, current_time)
# получаем значение последних 4 бит хеша виде целого числа
# это значение используется как индекс в массиве байтов хеша
start_index = INT(LAST_FOUR_BIT(hash))
# Получаем 4 байта, начиная с байта с номером start_index
four_bytes = GET_FOUR_BYTES(hash, start_index)
# преобразуем 4 байта в целое число
integer_four_bytes = INT(four_bytes)
# значение всё ещё может быть слишком длинным - обрезаем до нужной длины
code = integer_four_bytes % 10**(digits)
# возвращаем результат
return code