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

1

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

python
python-3.x
hmac
one-time-password
base32
asked on Stack Overflow Jun 7, 2019 by Kodiak • edited Jun 12, 2019 by marc_s

2 Answers

1

First, it's important to point out that the result of secret.encode('UTF-8') has exactly the same type as the result of base64.b32encode(bSecret) (and for that matter base64.b64encode(bSecret)) -- they all return bytes objects. Also worth noting is that the implementation of hmac in Python has no mention of base64/base32 encoding. So the short answer is that your expected result of 807244 is only valid if the shared secret is a base64/UTF-8 encoded blob.

This quick snippet shows that really you can give any bytes you like to hotp and it will come up with some result (because hotp is called multiple times in the example, counter is changed)

# ... everything from your example above ...
secret = "1234"
secret_bytes = secret.encode("UTF-8")
secret_bytes
>>> b'1234'
b32_secret = base64.b32encode(bSecret)
b32_secret
>>> b'GEZDGNA='
b64_secret = base64.b64encode(bSecret)
b64_secret
>>> b'MTIzNA=='
hotp(secret_bytes, counter)  # just a UTF-8 blob works
>>> '110366'
hotp(b32_secret, counter)  # base32/UTF-8 also works
>>> '896513'
hotp(b64_secret, counter)  # base64/UTF-8 works as well
>>> '806744'

If you have more detail of why you expected 807244 for a base32/UTF8 blob, I'll be happy to amend this answer.

answered on Stack Overflow Jun 8, 2019 by Andrew F
0

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)
answered on Stack Overflow Jun 10, 2019 by Kodiak

User contributions licensed under CC BY-SA 3.0