Я хотел назначить награду за мой вопрос, но мне удалось создать решение.Кажется, моя проблема связана с неправильным значением клавиши secret
(это должен быть правильный параметр для функции base64.b32decode()
).
Ниже я публикую полное рабочее решение с объяснением того, как его использовать.
Код
Следующий код достаточно.Я также загрузил его в GitHub в виде отдельного модуля с именем onetimepass (доступно здесь: https://github.com/tadeck/onetimepass).
import hmac, base64, struct, hashlib, time
def get_hotp_token(secret, intervals_no):
key = base64.b32decode(secret, True)
msg = struct.pack(">Q", intervals_no)
h = hmac.new(key, msg, hashlib.sha1).digest()
o = ord(h[19]) & 15
h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
return h
def get_totp_token(secret):
return get_hotp_token(secret, intervals_no=int(time.time())//30)
Он имеет две функции:
get_hotp_token()
генерирует одноразовый токен (который должен быть недействительным после однократного использования), get_totp_token()
генерирует токен на основе времени (измененный с 30-секундными интервалами),
Параметры
Когда дело касается параметров:
secret
- это секретное значение, известное серверу (приведенный выше скрипт) и клиенту (Google Authenticator, предоставляя его в качестве пароля в приложении), intervals_no
- это число, увеличиваемое после каждого поколения токена (это, вероятно, следует разрешить на сервере путем проверки некоторого конечного числа целых чисел после последнего успешного, проверенного в прошлом)
Как его использовать
- Генерировать
secret
(это должен быть правильный параметр для base64.b32decode()
) - желательно 16-символьный (без =
знаков), так как он наверняка работал для обоих сценариеви Google Authenticator. - Используйте
get_hotp_token()
, если вы хотите, чтобы одноразовые пароли были недействительными после каждого использования.В Google Authenticator этот тип паролей я упоминал как основанный на счетчике.Для проверки на сервере вам нужно будет проверить несколько значений intervals_no
(поскольку у вас нет гарантии, что пользователь по какой-то причине не сгенерировал проход между запросами), но не меньше, чем последнее действующее значение intervals_no
(таким образом, вы, вероятно, должны хранить его где-нибудь). - Используйте
get_totp_token()
, если хотите, чтобы токен работал с 30-секундными интервалами.Вы должны убедиться, что в обеих системах установлено правильное время (это означает, что они генерируют одну и ту же метку времени Unix в любой данный момент времени). - Обязательно защитите себя от атаки методом перебора.Если используется основанный на времени пароль, то попытка 1000000 значений менее чем за 30 секунд дает 100% шанс угадать пароль.В случае паролей на основе HMAC (HOTP) это выглядит еще хуже.
Пример
При использовании следующего кода для одноразового пароля на основе HMAC:
secret = 'MZXW633PN5XW6MZX'
for i in xrange(1, 10):
print i, get_hotp_token(secret, intervals_no=i)
вы получите следующий результат:
1 448400
2 656122
3 457125
4 35022
5 401553
6 581333
7 16329
8 529359
9 171710
, который соответствует токенам, сгенерированным приложением Google Authenticator (кроме случаев, когда короче 6 знаков, приложение добавляет нули к началу, чтобы достичьдлиной 6 символов).