pythonpython-3.xhmacone-time-passwordbase32

How to use base32 in combination with hotp (one time passwords) in python?


for a university exercise I want to develop a simple hotp server-client system in python. In this case the client sends a password and a one time password to the server. The server knows the secret, calculates the current hotp and compares the values it receives. So far, so good. With plaintext this works perfectly fine and the calculated values are the same I get when I use the iOS App "OTP Auth". But there is also the possibility to calculate the OTP in combination with base32. So I added a few lines to encode the plaintext to base32 but now the output in not correct.

Let's assume we're using the secret "1234" so the plaintext output would be "110366". That's working. But if I'm encoding the secret to base32 the output should be "807244" but my program calculates "896513". Anybody know why this is happening?

I've already tried to use different secrets and checked it on different apps. Always the same result.

import hmac
import hashlib
import array
import base64

counter = 0
digits = 6                      #Anzahl der Zeichen

def hotp(secret, c):
    global digits
    counter = extendCounter(c)
    hmac_sha1 = hmac.new(secret, counter, hashlib.sha1).hexdigest()
    return truncate(hmac_sha1)[-digits:]


def truncate(hmac_sha1):
    offset = int(hmac_sha1[-1], 16)
    binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff
    return str(binary)


def extendCounter(long_num):
    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 main():
    secret = "1234"
    bSecret = secret.encode("UTF-8")
    bSecret = base64.b32encode(bSecret)
    otp = hotp(bSecret, counter)
    one_time_password = otp

I expect 807244 as the output but the output is 896513


Solution

  • Found the mistake: Instead of translating the secret to base32, the secret must be a Base32 decoded value. Also instead of encoding this value, it must be decoded ("base64.b32decode(bytes(saved_secret, 'utf-8'))")

    So the correct main looks like this:

    def main():
        secret = "V6X27L5P" #Base32 value
        secret = base64.b32decode(bytes(secret, 'utf-8'))
        one_time_password = hotp(secret, counter)