I am trying to use one-time passwords that can be generated usingGoogle Authenticator application.
What Google Authenticator does
Basically, Google Authenticator implements two types of passwords:
- HOTP - HMAC-based One-Time Password, which means the password is changed with each call, in compliance toRFC4226, and
- TOTP - Time-based One-Time Password, which changes for every 30-seconds period (as far as I know).
Google Authenticator is also available as Open Source here:code.google.com/p/google-authenticator
Current code
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, timedef 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 hThe 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 multipleintervals_no values (exactly first 10000, beginning withintervals_no = 0), withsecret being equal to key provided within the GA app.
Questions I have
My questions are:
- 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 Answers3
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 ofsecret key (it must be correct parameter forbase64.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 calledonetimepass (available here:https://github.com/tadeck/onetimepass).
import hmac, base64, struct, hashlib, timedef 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 hdef get_totp_token(secret): return get_hotp_token(secret, intervals_no=int(time.time())//30)It has two functions:
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:
secretis a secret value known to server (the above script) and client (Google Authenticator, by providing it as password within application),intervals_nois 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 forbase64.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 ofintervals_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 workingintervals_novalue (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 4484002 6561223 4571254 350225 4015536 5813337 163298 5293599 171710which 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).
13 Comments
ord(h[19]) & 15 into :o = h[19] & 15 Thanks BTWreturn h withreturn '{:06}'.format(h)I wanted a python script to generate TOTP password. So, I wrote the python script. This is my implementation. I have thisinfo on wikipedia and some knowledge about HOTP and TOTP to write this script.
import hmac, base64, struct, hashlib, time, arraydef 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_arraydef 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 {digits} 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 {digits} length """ C = long(time.time() / window) return HOTP(K, C, digits=digits)2 Comments
By following the correct answer from @tadeck and @Anish-Shah, there is a simpler method to get the code without usingstruct and avoiding extra imports:
""" TOTP """import hmacimport timedef totp(key: bytes): """ Calculate TOTP using time and key """ now = int(time.time() // 30) msg = now.to_bytes(8, "big") digest = hmac.new(key, msg, "sha1").digest() offset = digest[19] & 0xF code = digest[offset : offset + 4] code = int.from_bytes(code, "big") & 0x7FFFFFFF code = code % 1000000 return "{:06d}".format(code)This works with Python 3.
You can get the current TOTP code by callingtotp(key) where the "key" is abytes (commonly the base 32 decoded key).
Comments
Explore related questions
See similar questions with these tags.
