The problem: my app fails to produce an identical TOTP as the one produced by Google Authenticator app when same secret key is used. Could someone tell me what went wrong, please. My focus is TOTP verification not secret key generation.
TIA
TIA
authenticator integration class:
Sub Class_Globals
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
End Sub
Sub VerifyTOTP(secretKey As String, userEnteredCode As String) As Boolean
Dim currentTimeSlice As Long = Floor(DateTime.Now / 30000) ' 30 seconds time slice
For i = -1 To 1 ' Check previous, current, and next time slices
Dim timeSlice As Long = currentTimeSlice + i
Dim expectedCode As String = GenerateTOTP(secretKey, timeSlice)
If expectedCode = userEnteredCode Then
Return True
End If
Next
Return False
End Sub
Sub GenerateTOTP(secretKey As String, timeSlice As Long) As String
' Dim keyBytes() As Byte = Base32Decode(secretKey) ' Decode Base32 secret to bytes
Dim timeSliceBytes() As Byte = LongToBytes(timeSlice)
Dim hmacBytes() As Byte = hmac_SHA1(timeSliceBytes, secretKey)
Dim offset As Int = Bit.And(hmacBytes(hmacBytes.Length - 1), 0xF)
Dim code As Long = Bit.And(hmacBytes(offset), 0x7F)
code = Bit.Or(Bit.ShiftLeft(code, 8), Bit.And(hmacBytes(offset + 1), 0xFF))
code = Bit.Or(Bit.ShiftLeft(code, 8), Bit.And(hmacBytes(offset + 2), 0xFF))
code = Bit.Or(Bit.ShiftLeft(code, 8), Bit.And(hmacBytes(offset + 3), 0xFF))
code = code Mod 1000000 ' 6-digit code
Return NumberFormat(code, 6, 0)
End Sub
Sub hmac_SHA1(data() As Byte, key As String) As Byte()
Try
Dim m As Mac
Dim k As KeyGenerator
k.Initialize("HMACSHA1")
' Corrected: Convert the key to raw bytes based on its Base32 encoding
Dim keyBytes() As Byte = Base32Decode(key)
k.KeyFromBytes(keyBytes)
m.Initialise("HMACSHA1", k.Key)
m.Update(data)
Dim b() As Byte = m.Sign
Return b
Catch
Log("Error in hmac_SHA1: " & LastException.Message)
Return Null
End Try
End Sub
Sub LongToBytes(value As Long) As Byte()
Dim bytes(8) As Byte ' A long value has 8 bytes
For i = 0 To 7
bytes(7 - i) = Bit.And(Bit.ShiftRight(value, i * 8), 0xFF)
Next
Return bytes
End Sub
Sub Base32Decode(encoded As String) As Byte()
Dim Base32Chars As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
Dim bits As Int = 0
Dim value As Long = 0
Dim buffer As List
buffer.Initialize
For i = 0 To encoded.Length - 1
value = Bit.Or(Bit.ShiftLeft(value, 5), Base32Chars.IndexOf(encoded.CharAt(i)))
bits = bits + 5
If bits >= 8 Then
buffer.Add(Bit.And(Bit.ShiftRight(value, bits - 8), 0xFF))
bits = bits - 8
End If
Next
' Convert the list of bytes to a byte array
Dim byteArray(buffer.Size) As Byte
For i = 0 To buffer.Size - 1
byteArray(i) = buffer.Get(i)
Next
Return byteArray
End Sub
test code:
#Region Shared Files
#CustomBuildAction: folders ready, %WINDIR%\System32\Robocopy.exe,"..\..\Shared Files" "..\Files"
'Ctrl + click to sync files: ide://run?file=%WINDIR%\System32\Robocopy.exe&args=..\..\Shared+Files&args=..\Files&FilesSync=True
#End Region
'Ctrl + click to export as zip: ide://run?File=%B4X%\Zipper.jar&Args=GoogleAuthenticatorIntegration.zip
Sub Class_Globals
Private Root As B4XView
Private xui As XUI
Dim googleAuthenticator As clsGoogleAuthenticator
Private imvQrCode As ImageView
Private secretKey As String="KGJ3U23QMMXDLFOF" 'hard coded secret key for testing, 16 alphanumeric chars
Private B4XFloatTextField1 As B4XFloatTextField
End Sub
Public Sub Initialize
' B4XPages.GetManager.LogEvents = True
googleAuthenticator.Initialize
End Sub
'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
Root = Root1
Root.LoadLayout("MainPage")
End Sub
'You can see the list of page related events in the B4XPagesManager object. The event name is B4XPage.
Private Sub Button1_Click
'xui.MsgboxAsync("Hello world!", "B4X")
secretKey=googleAuthenticator.GenerateSecretKey
'secretKey="KGJ3U23QMMXDLFOF" 'hard coded secret key for testing
End Sub
Private Sub btnVerify_Click
Dim userCode As String=B4XFloatTextField1.Text.Trim
If googleAuthenticator.VerifyTOTP(secretKey, userCode) Then
Log("Verified")
Else
Log("Failed")
End If
End Sub