Google Authenticator implementation in Python

后端 未结 2 1965
抹茶落季
抹茶落季 2020-11-30 15:51

I am trying to use one-time passwords that can be generated using Google Authenticator application.

What Google Authenticator does

Basically, Google Authen

相关标签:
2条回答
  • 2020-11-30 16:47

    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 {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)
    
    0 讨论(0)
  • 2020-11-30 16:53

    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)
    

    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:

    • 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

    1. 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.
    2. 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).
    3. 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).
    4. 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).

    0 讨论(0)
提交回复
热议问题