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
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.
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)
User contributions licensed under CC BY-SA 3.0