0

First of all I am not a native English speaker and new here. It is also not a question, but a possible solution.

I've been looking for a complete solution on how to implement the Google Authenticator App function in VBA. But all hmac and sha1 examples I found give wrong results.

Therefore, using other programming languages, I reproduced the logic. Sources that helped me from the community are given in the source code.

The code works for me, but I am an amateur in VBA. Maybe it is helpful and any optimization is welcome to me.

Private Type SYSTEMTIME
    wYear As Integer
    wMonth As Integer
    wDayOfWeek As Integer
    wDay As Integer
    wHour As Integer
    wMinute As Integer
    wSecond As Integer
    wMilliseconds As Integer
End Type
Private Declare Sub GetSystemTime Lib "Kernel32" (ByRef lpSystemTime As SYSTEMTIME)

Sub otp()
    Dim Key As String, Secret As String, Time As String, hmac As String, ch As String, p1 As String, p2 As String, otp As String
    Dim offset As Long, d As Long, i As Long, n As Long, j As Long, a As Long
    Secret = UCase("<replace with secret from QR>")
    If Len(Secret) >= 20 Then
        n = 0
        j = 0
    
        ' stackoverflow.com/questions/60327784/converting-from-base-32-to-decimal-without-vba
        ' Change: use base32 variant
        For i = 1 To Len(Secret)
            ch = Mid$(Secret, i, 1)
            If ch >= "A" And ch <= "Z" Then
                d = asc(ch) - asc("A")
            ElseIf ch >= "2" And ch <= "7" Then
                d = asc(ch) - asc("0") + 24
            End If
            ' www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
            ' reproduced
            n = (ShiftLeft(n, 5)) + d
            j = j + 5
            If j >= 8 Then
                j = j - 8
                Key = Key & WorksheetFunction.Dec2Hex(ShiftRight((n And ShiftLeft(255, j)), j), 2)
            End If
        Next i
        Time = Right("0000000000000000" & Hex(WorksheetFunction.Floor(CurrentTimeMillis() / 1000 / 30, 1)), 16)
        hmac = HEX_HMACSHA1(Time, Key)
        offset = Hex2UInt(Right(hmac, 1))
        p2 = Hex2UInt("7fffffff")
        If offset = 0 Then
            p1 = Hex2UInt(Left(hmac, 8))
        Else
            p1 = Hex2UInt(Mid(hmac, offset * 2 + 1, 8))
        End If
        otp = Right(WorksheetFunction.Bitand(p1, p2), 6)
        Debug.Print otp
    End If
    
End Sub


' stackoverflow.com/questions/29772224/get-unix-time-milliseconds
Function CurrentTimeMillis() As Double
    ' Returns the milliseconds from 1970/01/01 00:00:00.0 to system UTC
    Dim st As SYSTEMTIME
    GetSystemTime st
    Dim t_Start, t_Now
    t_Start = DateSerial(1970, 1, 1) ' Starting time for Linux
    t_Now = DateSerial(st.wYear, st.wMonth, st.wDay) + _
        TimeSerial(st.wHour, st.wMinute, st.wSecond)
    CurrentTimeMillis = DateDiff("s", t_Start, t_Now) * 1000 + st.wMilliseconds
End Function

' stackoverflow.com/questions/40213758/convert-hex-string-to-unsigned-int-vba
Function Hex2UInt(h As String) As Double
    Dim dbl As Double: dbl = CDbl("&h" & h)

    If dbl < 0 Then
        dbl = CDbl("&h1" & h) - 4294967296#
    End If

    Hex2UInt = dbl
End Function

' mrexcel.com/board/threads/hmac-sha1-hash-hex-input-string.1137771/
' Change: use byte array with unsigned integer and return hex with leading zeros
Public Function HEX_HMACSHA1(ByVal sTextToHash As String, ByVal sSharedSecretKey As String) As String
    Dim asc                 As Object
    Dim enc                 As Object
    Dim TextToHash()        As Byte
    Dim SharedSecretKey()   As Byte
    Dim Bytes()             As Byte
    Dim sHexString          As String
    Dim i                   As Long
    
    Set asc = CreateObject("System.Text.UTF8Encoding")
    Set enc = CreateObject("System.Security.Cryptography.HMACSHA1")
    
    TextToHash = HexStringToByteArray(sTextToHash)
    SharedSecretKey = HexStringToByteArray(sSharedSecretKey)
    enc.Key = SharedSecretKey
    
    Bytes = enc.ComputeHash_2((TextToHash))
    HEX_HMACSHA1 = ByteArrayToHexStr(Bytes)
    Set asc = Nothing
    Set enc = Nothing
End Function

' stackoverflow.com/questions/44979363/excel-vba-regex-function-that-returns-multiple-matches-into-a-single-cell
' Change: create byte array with unsigned integer
Function HexStringToByteArray(strInput As String) As Byte()

Dim rMatch As Object
Dim s As String
Dim arrayMatches() As Byte
Dim i As Long


With New RegExp
    .Global = True
    .MultiLine = True
    .IgnoreCase = True
    .Pattern = "([A-F0-9]{2})"
    If .Test(strInput) Then
        For Each rMatch In .Execute(strInput)
            ReDim Preserve arrayMatches(i)
            arrayMatches(i) = Hex2UInt(rMatch.Value)
            i = i + 1
        Next
    End If
End With

    HexStringToByteArray = arrayMatches
End Function

' https://www.vbforums.com/showthread.php?559398-Byte-array-to-hex-string
' Change: removed Spaces between Hex
Function ByteArrayToHexStr(b() As Byte) As String
   Dim n As Long, i As Long
   
   ByteArrayToHexStr = Space$(2 * (UBound(b) - LBound(b)) + 2)
   n = 1
   For i = LBound(b) To UBound(b)
      Mid$(ByteArrayToHexStr, n, 2) = Right$("00" & Hex$(b(i)), 2)
      n = n + 2
   Next
End Function

' xbeat.net/vbspeed/c_ShiftLeft.htm
Public Static Function ShiftLeft(ByVal Value As Long, ByVal ShiftCount As Long) As Long
' by Jost Schwider, jost@schwider.de, 20010928
  Dim Pow2(0 To 31) As Long
  Dim i As Long
  Dim mask As Long
  
  Select Case ShiftCount
  Case 1 To 31
  
    'Ggf. Initialisieren:
    If i = 0 Then
      Pow2(0) = 1
      For i = 1 To 30
        Pow2(i) = 2 * Pow2(i - 1)
      Next i
    End If
    
    'Los gehts:
    mask = Pow2(31 - ShiftCount)
    If Value And mask Then
      ShiftLeft = (Value And (mask - 1)) * Pow2(ShiftCount) Or &H80000000
    Else
      ShiftLeft = (Value And (mask - 1)) * Pow2(ShiftCount)
    End If
  
  Case 0
  
    ShiftLeft = Value
  
  End Select
End Function

' xbeat.net/vbspeed/c_ShiftRight.htm
Public Static Function ShiftRight(ByVal Value As Long, ByVal ShiftCount As Long) As Long
' by Donald, donald@xbeat.net, 20011009
  Dim lPow2(0 To 30) As Long
  Dim i As Long
  
  Select Case ShiftCount
  Case 0 To 30
    If i = 0 Then
      lPow2(0) = 1
      For i = 1 To 30
        lPow2(i) = 2 * lPow2(i - 1)
      Next
    End If
    If Value And &H80000000 Then
      ShiftRight = Int(Value / lPow2(ShiftCount))
    Else
      ShiftRight = Value \ lPow2(ShiftCount)
    End If
  Case 31
    If Value And &H80000000 Then
      ShiftRight = -1
    Else
      ShiftRight = 0
    End If
  End Select
End Function

0 Answers0