You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于VBA实现Google Authenticator的双因素认证(2FA)方案

VBA实现Google Authenticator动态验证码(TOTP)完整方案

我不是英语母语者,这是我第一次在这里分享内容。以下是一个可行的解决方案,而非提问:

我曾在网上寻找VBA实现Google Authenticator功能的完整方案,但找到的所有HMAC和SHA1示例都无法得到正确结果。于是我参考其他编程语言的实现逻辑,重现了这套代码,相关参考逻辑的说明已标注在代码注释中。

代码可以正常运行,但我只是VBA业余爱好者,欢迎大家提出优化建议。

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
    
        ' 参考社区Base32转码逻辑,调整为适配当前需求的实现
        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
            ' 参考TOTP算法实现逻辑,重现该步骤
            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


' 获取UTC时间戳(毫秒级)的实现
Function CurrentTimeMillis() As Double
    ' 返回从1970/01/01 00:00:00.0到当前系统UTC时间的毫秒数
    Dim st As SYSTEMTIME
    GetSystemTime st
    Dim t_Start, t_Now
    t_Start = DateSerial(1970, 1, 1) ' Unix时间起始点
    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

' 十六进制字符串转无符号整数
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

' HMAC-SHA1哈希实现,调整为使用无符号字节数组并返回带前导零的十六进制字符串
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

' 十六进制字符串转字节数组,调整为生成无符号字节数组
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

' 字节数组转十六进制字符串,去掉了十六进制字符间的空格
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

' 左移位运算实现
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
  
    ' 初始化幂次数组
    If i = 0 Then
      Pow2(0) = 1
      For i = 1 To 30
        Pow2(i) = 2 * Pow2(i - 1)
      Next i
    End If
    
    ' 执行左移位
    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

' 右移位运算实现
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

内容的提问来源于stack exchange,提问作者Stephan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 00:20:30