Google Authenticator implementation in Python
I was looking for existing solutions to generate HOTP and TOTP passwords, but did not find much. The code I have is the following snippet responsible for generating HOTP:
import hmac, base64, struct, hashlib, time def get_token(secret, digest_mode=hashlib.sha1, intervals_no=None): if intervals_no == None: intervals_no = int(time.time()) // 30 key = base64.b32decode(secret) msg = struct.pack(">Q", intervals_no) h = hmac.new(key, msg, digest_mode).digest() o = ord(h[19]) & 15 h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000 return h
The problem I am facing is that the password I generate using the above code is not the same as generated using Google Authenticator app for Android. Even though I tried multiple intervals_no values (exactly first 10000, beginning with intervals_no = 0 ), with secret being equal to key provided within the GA app.
Questions I have
- What am I doing wrong?
- How can I generate HOTP and/or TOTP in Python?
- Are there any existing Python libraries for this?
To sum up: please give me any clues that will help me implement Google Authenticator authentication within my Python code.
3 Answers 3
I wanted to set a bounty on my question, but I have succeeded in creating solution. My problem seemed to be connected with incorrect value of secret key (it must be correct parameter for base64.b32decode() function).
Below I post full working solution with explanation on how to use it.
Code
The following code is enough. I have also uploaded it to GitHub as separate module called onetimepass (available here: 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() generates one-time token (that should invalidate after single use),
- get_totp_token() generates token based on time (changed in 30-second intervals),
Parameters
When it comes to parameters:
- secret is a secret value known to server (the above script) and client (Google Authenticator, by providing it as password within application),
- intervals_no is the number incremeneted after each generation of the token (this should be probably resolved on the server by checking some finite number of integers after last successful one checked in the past)
How to use it
- Generate secret (it must be correct parameter for base64.b32decode() ) — preferably 16-char (no = signs), as it surely worked for both script and Google Authenticator.
- Use get_hotp_token() if you want one-time passwords invalidated after each use. In Google Authenticator this type of passwords i mentioned as based on the counter. For checking it on the server you will need to check several values of intervals_no (as you have no quarantee that user did not generate the pass between the requests for some reason), but not less than the last working intervals_no value (thus you should probably store it somewhere).
- Use get_totp_token() , if you want a token working in 30-second intervals. You have to make sure both systems have correct time set (meaning that they both generate the same Unix timestamp in any given moment in time).
- Make sure to protect yourself from brute-force attack. If time-based password is used, then trying 1000000 values in less than 30 seconds gives 100% chance of guessing the password. In case of HMAC-based passowrds (HOTPs) it seems to be even worse.
Example
When using the following code for one-time HMAC-based password:
secret = 'MZXW633PN5XW6MZX' for i in xrange(1, 10): print i, get_hotp_token(secret, intervals_no=i)
you will get the following result:
1 448400 2 656122 3 457125 4 35022 5 401553 6 581333 7 16329 8 529359 9 171710
which is corresponding to the tokens generated by the Google Authenticator app (except if shorter than 6 signs, app adds zeros to the beginning to reach a length of 6 chars).
@burhan: If you need the code, I have uploaded it also to GitHub (here: https://github.com/tadeck/onetimepass), so it should be quite easy to use it within projects as separate module. Enjoy!
I had a problem with this code because the ‘secret’ I was provided by the service I’m trying to log into was lowercase, not uppercase. Changing line 4 to read «key = base64.b32decode(secret, True)» fixed the problem for me.
I was just given a 23 character secret by a site. Your code fails with a «TypeError: Incorrect padding» when I give it that secret. Padding the secret, like this, fixed the problem: key = base64.b32decode(secret + ‘===='[:3-((len(secret)-1)%4)], True)
I suggest to change get_totp_token() to always return a string of 6 digits. Replace return h with return ‘<:06>‘.format(h)
I wanted a python script to generate TOTP password. So, I wrote the python script. This is my implementation. I have this info on wikipedia and some knowledge about HOTP and TOTP to write this script.
import hmac, base64, struct, hashlib, time, array def Truncate(hmac_sha1): """ Truncate represents the function that converts an HMAC-SHA-1 value into an HOTP value as defined in Section 5.3. http://tools.ietf.org/html/rfc4226#section-5.3 """ offset = int(hmac_sha1[-1], 16) binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff return str(binary) def _long_to_byte_array(long_num): """ helper function to convert a long number into a byte array """ byte_array = array.array('B') for i in reversed(range(0, 8)): byte_array.insert(0, long_num & 0xff) long_num >>= 8 return byte_array def HOTP(K, C, digits=6): """ HOTP accepts key K and counter C optional digits parameter can control the response length returns the OATH integer code with length """ C_bytes = _long_to_byte_array(C) hmac_sha1 = hmac.new(key=K, msg=C_bytes, digestmod=hashlib.sha1).hexdigest() return Truncate(hmac_sha1)[-digits:] def TOTP(K, digits=6, window=30): """ TOTP is a time-based variant of HOTP. It accepts only key K, since the counter is derived from the current time optional digits parameter can control the response length optional window parameter controls the time window in seconds returns the OATH integer code with length """ C = long(time.time() / window) return HOTP(K, C, digits=digits)
Python. Двухфакторная авторизация через Google Authenticator
Последние годы стало модно делать двухфакторную авторизацию, это безусловно повышает безопасность приложений и сайтов в частности, сегодня я расскажу как сделать дополнительную авторизацию через TOTP. В качестве клиента будем использовать мобильную версию Google Authenticator для Android.
Для этого нам понадобится два файла:
- «totp.py» — собственно реализация алгоритма TOTP;
- «totp_auth.py» — хелпер для генерации и валидации токенов.
Пример работы
Создадим изолированное окружение, установим зависимости:
mkdir ~/work/totp && cd ~/work/totp wget -O totp.py https://gist.githubusercontent.com/adw0rd/0a61f53dd7a61e5aa65c/raw/totp.py wget -O totp_auth.py https://gist.githubusercontent.com/adw0rd/0a61f53dd7a61e5aa65c/raw/totp_auth.py virtualenv --no-site-packages --distribute venv . venv/bin/activate pip install qrcode PIL
Создадим файл sample.py для демонстрации работы, со следующим содержимым:
Разберем работу sample.py. Сначала идет шаг регистрации двухфакторной авторизации пользователя и сохранения секретного ключа (SECRET):
- Генерируем новый секретный ключ (SECRET);
- Выводим QR-код;
- Пользователь его сканирует через Google Authenticator;
- Вводит в форму ввода на сайте полученный от Google Authenticator токен;
- Если Пользователь ввел правилный токен, то мы сохраняем секретный ключ (SECRET) этому пользователю в БД, чтобы при авторизации использовался этот ключ для валидации токенов с Google Authenticator.
$ python sample.py SECRET: UG4QTQ6F4JI4BE3Y
Please input the token from Google Authenticator: 123123 FAILED (тут специально пользователь ввел неверный токен) Please input the token from Google Authenticator: 613257 SUCCESS (тут пользователь ввел верный токен, сохраняем SECRET в БД)
Теперь разберем шаги авторизации:
- Пользователь входит на сайт и вводит правильные логин и пароль;
- Если мы однозначно идентифицируем пользователя и для него уже есть секретный ключ (SECRET) в БД, то выводим ему QR-код по этому секретному ключу;
- Он сканирует QR-код через Google Authenticator, либо среди свои аккаунтов находит уже созданный и вводит текущий токен на сайте;
- Если он за указанное время ввел токен, то авторизация считается успешной иначе ему предлагается повторить попытку.
Запускаем с секретным ключем:
$ python sample.py UG4QTQ6F4JI4BE3Y SECRET: UG4QTQ6F4JI4BE3Y
Please input the token from Google Authenticator: 123123 FAILED (тут специально пользователь ввел неверный токен) Please input the token from Google Authenticator: 394500 SUCCESS (тут пользователь ввел верный токен, можно его авторизовывать)
Таким образом мы реализовали двухфакторную авторизацию на базе TOTP и Google Authenticator. Постараюсь в ближайшее время на базе этой статьи сделать приложение для Django, которое будет хранить секретные ключи в БД и выводить QR-код когда это необходимо.