-1

I'm trying to implement TOTP on 8086 assembly. The procedures that return unix time/30 and HMAC-SHA1 are working perfectly(checked). I'm using the key "0000000000" which is equal to 0x30303030303030303030(GAYDAMBQGAYDAMBQ in base32) and I'm getting different results than the Google Authenticator app. This is my code:

proc GoogleAuthenticator
    call EpochTimeDiv30 ;get epoch time in seconds/30 in dx:ax
    xchg dh, dl ;we need it to be big endian in the memory
    xchg ah, al
;---------------------HMAC-SHA1 preparation------------------
    mov [word HmacMsg], dx 
    mov [word HmacMsg+2], ax ;msg is epoch time
    mov [HmacMsgLen], 4 ;msg length is 4bytes
    mov [HKeyLen], 10 ;key length is 10bytes
    lea bx, [HmacMsg]
    mov [HmacMsgOffset], bx ;put the offset of the msg
    call HMAC_SHA1 ;Key is already in Key var
;Now the result is in msgHash (result is big-endian)

    mov al, [msgHash+19] ;last byte of hashed msg
    and al, 0Fh ;we need only the last nibble
    xor ah, ah
    mov si, ax
    mov dx, [word msgHash+si] ;get offset, offset+1
    mov ax, [word msgHash+si+2] ;get offfset+2, offset+3
    xchg dh, dl ;back to big endian
    xchg ah, al ;back to big endian

    and dh, 7Fh ;removing the most significant bit(MSB)

    Mod32 0Fh, 4240h ;dx:ax modulo 1,000,000
    ret
endp GoogleAuthenticator    

EDIT: The algorithm I'm trying to implement:

  function GoogleAuthenticatorCode(string secret)
      key := 5B5E7MMX344QRHYO
      message := floor(current Unix time / 30)
      hash := HMAC-SHA1(key, message)
      offset := last nibble of hash
      truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
      Set the first bit of truncatedHash to zero  //remove the most significant bit
      code := truncatedHash mod 1000000
      pad code with 0 from the left until length of code is 6
      return code
Shachaf Zohar
  • 165
  • 1
  • 13
  • 2
    Since you are using the time as input how do you even expect your result to be the same? Anyway, show some input (time) along with expected and actual results. Also some description of the algorithm you are trying to implement would be helpful. – Jester Apr 27 '19 at 14:01
  • @Jester I'm not using the time as input. The procedure EpochTimeDiv30 returns the current unix time in dx:ax. See the last edit for the algorithm – Shachaf Zohar Apr 27 '19 at 14:07

1 Answers1

1

I've found the problem. The messsage should be 8 bytes long. This is the working code:

proc GoogleAuthenticator
    call EpochTimeDiv30 ;get epoch time in seconds/30 in dx:ax
    xchg dh, dl ;we need it to be big endian in the memory
    xchg ah, al
;---------------------HMAC-SHA1 preparation------------------
    mov [word HmacMsg], 0
    mov [word HmacMsg+2], 0
    mov [word HmacMsg+4], dx 
    mov [word HmacMsg+6], ax ;msg is epoch time
    mov [HmacMsgLen], 8 ;msg length is 4bytes
    mov [HKeyLen], 10 ;key length is 10bytes
    lea bx, [HmacMsg]
    mov [HmacMsgOffset], bx ;put the offset of the msg
    call HMAC_SHA1 ;Key is already in Key var
;Now the result is in msgHash (result is big-endian)

    mov al, [msgHash+19] ;last byte of hashed msg
    and al, 0Fh ;we need only the last nibble
    xor ah, ah
    mov si, ax
    mov dx, [word msgHash+si] ;get offset, offset+1
    mov ax, [word msgHash+si+2] ;get offfset+2, offset+3
    xchg dh, dl ;back to big endian
    xchg ah, al ;back to big endian

    and dh, 7Fh ;removing the most significant bit(MSB)

    Mod32 0Fh, 4240h ;dx:ax modulo 1,000,000
    ret
endp GoogleAuthenticator
Shachaf Zohar
  • 165
  • 1
  • 13
  • `and ax, 0Fh` would be more efficient than separately zeroing AH, in code size and performance cost on all CPUs including the original 8086 you're apparently writing for. – Peter Cordes Apr 27 '19 at 17:36
  • When will this implementation start failing? i.e. when will the actual `unix_time/30` not fit in 32 bits? I guess not for a long time; raw 32-bit Unix time rolls over in Year 2038 https://en.wikipedia.org/wiki/Year_2038_problem. So dividing by 30 makes it good until about `(2038-1970)*30 + 1970` = the year 4010, for your implementation that assumes the upper 32 bits will always be zero instead of actually using signed 64-bit time. (Or maybe an extra factor of 2 there, since you zero-extend to 64-bit, not sign-extend.) – Peter Cordes Apr 27 '19 at 18:07