Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
TOTP 100% qb64 Yup - sorta LOL
#11
Thank you very much! I forgot I had this over here. I couldn't remember where I posted it at. Rep up for you !
3 out of 2 people have trouble with fractions

Reply
#12
I have no idea how accurate this is but I had Gemini Pro use my custom NotebookLM trained on QB64 PE v4.1.0 help files. I have not tested this. I had to do a minimal edit to some variable names as it wanted to use reserved keywords as variable names.

Code: (Select All)
' ==============================================================================
' QB64-PE TOTP (Time-based One-Time Password) Generator
' Implements RFC 6238
'
' This program generates 6-digit MFA codes compatible with Google Authenticator,
' Microsoft Authenticator, etc.
'
' Requirements: QB64-PE (Phoenix Edition) v3.1.0+ for _ROL, _SHL, _SHR
' ==============================================================================

$Console:Only
_Dest _Console

Dim secret__key As String
Dim totpCode As String
Dim lastCode As String
Dim unixTime As _Integer64
Dim remainingSeconds As Integer

Print "========================================"
Print "      QB64-PE TOTP Generator"
Print "========================================"
Print "Enter your Base32 Secret Key (spaces are ignored):"
Input "> ", secret__key

' Clean the key (remove spaces, uppercase)
secret__key = CleanBase32$(secret__key)

If Len(secret__key) = 0 Then
    Print "Error: Key cannot be empty."
    End
End If

Print "Generating codes..."
Print "Press Ctrl+C to exit."
Print

Do
    ' 1. Get current time in Unix format (seconds since Jan 1 1970)
    unixTime = GetUnixTime%%

    ' 2. Calculate TOTP Step (Time / 30)
    Dim timeStep As _Integer64
    timeStep = unixTime \ 30

    ' 3. Calculate remaining seconds in this window for display
    remainingSeconds = 30 - (unixTime Mod 30)

    ' 4. Generate the HMAC-SHA1 Hash
    '    We need to treat the timeStep as an 8-byte big-endian integer
    Dim msg As String
    msg = Int64ToBytes$(timeStep)

    '    Decode the Base32 secret to raw bytes
    Dim __keyBytes As String
    __keyBytes = Base32Decode$(secret__key)

    '    Compute HMAC-SHA1
    Dim hmac As String
    hmac = HMAC_SHA1$(__keyBytes, msg)

    ' 5. Dynamic Truncation (RFC 4226)
    '    Get the last byte of the hash
    Dim offset As Integer
    offset = Asc(Right$(hmac, 1)) And &H0F

    '    Get 4 bytes starting at that offset
    Dim binaryCode As _Integer64
    Dim byte1 As _Integer64, byte2 As _Integer64, byte3 As _Integer64, byte4 As _Integer64

    byte1 = Asc(Mid$(hmac, offset + 1, 1)) And &H7F ' Mask MSB
    byte2 = Asc(Mid$(hmac, offset + 2, 1)) And &HFF
    byte3 = Asc(Mid$(hmac, offset + 3, 1)) And &HFF
    byte4 = Asc(Mid$(hmac, offset + 4, 1)) And &HFF

    '    Combine bytes into a 32-bit integer (using _INTEGER64 to avoid overflow issues)
    binaryCode = _ShL(byte1, 24) Or _ShL(byte2, 16) Or _ShL(byte3, 8) Or byte4

    ' 6. Generate the 6-digit code
    Dim otp As _Integer64
    otp = binaryCode Mod 1000000

    '    Format as 6 digits with leading zeros
    totpCode = Right$("000000" + _Trim$(Str$(otp)), 6)

    ' 7. Display
    ' Only redraw if the code changed or just to update the timer
    Locate 8, 1
    Print "Current Code: "; totpCode
    Print "Next code in: "; remainingSeconds; "s  "

    _Limit 5 ' Update 5 times a second
Loop

' ==============================================================================
' FUNCTIONS & SUBS
' ==============================================================================

' ------------------------------------------------------------------------------
' Calculates HMAC-SHA1
' RFC 2104: HMAC(K, text) = H((K XOR opad) + H((K XOR ipad) + text))
' ------------------------------------------------------------------------------
Function HMAC_SHA1$ (__key As String, message As String)
    Dim k As String
    k = __key

    ' SHA1 Block size is 64 bytes
    Const BLOCK_SIZE = 64

    ' If key is longer than block size, hash it first
    If Len(k) > BLOCK_SIZE Then
        k = HexToStr$(SHA1$(k))
    End If

    ' Pad key with zeros if shorter than block size
    If Len(k) < BLOCK_SIZE Then
        k = k + String$(BLOCK_SIZE - Len(k), 0)
    End If

    ' Create Inner and Outer pads
    Dim ipad As String, opad As String
    ipad = String$(BLOCK_SIZE, 0)
    opad = String$(BLOCK_SIZE, 0)

    Dim i As Integer
    For i = 1 To BLOCK_SIZE
        ' ipad = 0x36, opad = 0x5c
        Mid$(ipad, i, 1) = Chr$(Asc(Mid$(k, i, 1)) Xor &H36)
        Mid$(opad, i, 1) = Chr$(Asc(Mid$(k, i, 1)) Xor &H5C)
    Next i

    ' Perform Inner Hash: SHA1(ipad + message)
    Dim innerHashStr As String
    innerHashStr = HexToStr$(SHA1$(ipad + message))

    ' Perform Outer Hash: SHA1(opad + innerHash)
    ' This is the final result
    HMAC_SHA1$ = HexToStr$(SHA1$(opad + innerHashStr))
End Function

' ------------------------------------------------------------------------------
' Custom SHA1 Implementation (Since _SHA1 is not available)
' Returns a 40-character Hex String
' ------------------------------------------------------------------------------
Function SHA1$ (text As String)
    Dim h0 As _Unsigned Long, h1 As _Unsigned Long, h2 As _Unsigned Long, h3 As _Unsigned Long, h4 As _Unsigned Long
    h0 = &H67452301: h1 = &HEFCDAB89: h2 = &H98BADCFE: h3 = &H10325476: h4 = &HC3D2E1F0

    Dim message_len As _Unsigned _Integer64
    message_len = Len(text) * 8 ' Length in bits

    ' Pre-processing: Append '1' bit (byte 0x80)
    Dim processing As String
    processing = text + Chr$(&H80)

    ' Append 0 bits until length = 448 mod 512
    While (Len(processing) Mod 64) <> 56
        processing = processing + Chr$(0)
    Wend

    ' Append original length as 64-bit big-endian integer
    Dim bits As _Unsigned _Integer64
    bits = message_len
    Dim lenStr As String
    lenStr = String$(8, 0)
    Dim i As Integer
    For i = 8 To 1 Step -1
        Mid$(lenStr, i, 1) = Chr$(bits And &HFF)
        bits = _ShR(bits, 8)
    Next i
    processing = processing + lenStr

    ' Process in 512-bit (64-byte) chunks
    Dim chunkCount As Long
    chunkCount = Len(processing) \ 64

    Dim w(0 To 79) As _Unsigned Long
    Dim a As _Unsigned Long, b As _Unsigned Long, c As _Unsigned Long, d As _Unsigned Long, e As _Unsigned Long
    Dim f As _Unsigned Long, k As _Unsigned Long, temp As _Unsigned Long
    Dim chunkIndex As Long

    For chunkIndex = 0 To chunkCount - 1
        Dim chunk As String
        chunk = Mid$(processing, (chunkIndex * 64) + 1, 64)

        ' Break chunk into sixteen 32-bit big-endian words
        For i = 0 To 15
            w(i) = CVL_BE(Mid$(chunk, (i * 4) + 1, 4))
        Next i

        ' Extend into eighty 32-bit words
        For i = 16 To 79
            w(i) = _RoL(w(i - 3) Xor w(i - 8) Xor w(i - 14) Xor w(i - 16), 1)
        Next i

        a = h0: b = h1: c = h2: d = h3: e = h4

        For i = 0 To 79
            If i <= 19 Then
                f = (b And c) Or ((Not b) And d)
                k = &H5A827999
            ElseIf i <= 39 Then
                f = b Xor c Xor d
                k = &H6ED9EBA1
            ElseIf i <= 59 Then
                f = (b And c) Or (b And d) Or (c And d)
                k = &H8F1BBCDC
            Else
                f = b Xor c Xor d
                k = &HCA62C1D6
            End If

            temp = _RoL(a, 5) + f + e + k + w(i)
            e = d
            d = c
            c = _RoL(b, 30)
            b = a
            a = temp
        Next i

        h0 = h0 + a
        h1 = h1 + b
        h2 = h2 + c
        h3 = h3 + d
        h4 = h4 + e
    Next chunkIndex

    SHA1$ = Hex8$(h0) + Hex8$(h1) + Hex8$(h2) + Hex8$(h3) + Hex8$(h4)
End Function

' ------------------------------------------------------------------------------
' Helper: Convert 4-byte BE string to Unsigned Long
' ------------------------------------------------------------------------------
Function CVL_BE~& (s As String)
    Dim b1 As _Unsigned Long, b2 As _Unsigned Long, b3 As _Unsigned Long, b4 As _Unsigned Long
    b1 = Asc(Mid$(s, 1, 1))
    b2 = Asc(Mid$(s, 2, 1))
    b3 = Asc(Mid$(s, 3, 1))
    b4 = Asc(Mid$(s, 4, 1))
    CVL_BE~& = _ShL(b1, 24) Or _ShL(b2, 16) Or _ShL(b3, 8) Or b4
End Function

' ------------------------------------------------------------------------------
' Helper: Return 8-digit hex string from Unsigned Long
' ------------------------------------------------------------------------------
Function Hex8$ (n As _Unsigned Long)
    Dim h As String
    h = Hex$(n)
    Hex8$ = Right$("00000000" + h, 8)
End Function

' ------------------------------------------------------------------------------
' Converts a Base32 string to Raw Bytes
' ------------------------------------------------------------------------------
Function Base32Decode$ (__inStr As String)
    Dim chars As String
    chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"

    Dim bitBuffer As _Integer64
    Dim bitCount As Integer
    Dim outStr As String
    Dim i As Integer, valIdx As Integer
    Dim char As String

    bitBuffer = 0
    bitCount = 0
    outStr = ""

    For i = 1 To Len(__inStr)
        char = Mid$(__inStr, i, 1)
        ' Find index in base32 charset
        valIdx = InStr(chars, char) - 1

        If valIdx >= 0 Then
            ' Shift buffer left 5 bits and add new value
            bitBuffer = _ShL(bitBuffer, 5) Or valIdx
            bitCount = bitCount + 5

            ' If we have at least 8 bits, extract a byte
            If bitCount >= 8 Then
                bitCount = bitCount - 8
                Dim byteVal As Integer
                byteVal = _ShR(bitBuffer, bitCount) And &HFF
                outStr = outStr + Chr$(byteVal)
            End If
        End If
    Next i

    Base32Decode$ = outStr
End Function

' ------------------------------------------------------------------------------
' Cleans Base32 input (removes spaces, converts to uppercase)
' ------------------------------------------------------------------------------
Function CleanBase32$ (__inStr As String)
    Dim outStr As String
    Dim i As Integer
    Dim c As String

    outStr = ""
    For i = 1 To Len(__inStr)
        c = UCase$(Mid$(__inStr, i, 1))
        If (c >= "A" And c <= "Z") Or (c >= "2" And c <= "7") Then
            outStr = outStr + c
        End If
    Next i
    CleanBase32$ = outStr
End Function

' ------------------------------------------------------------------------------
' Converts an _INTEGER64 to an 8-byte Big-Endian Binary String
' Required for the TOTP time counter
' ------------------------------------------------------------------------------
Function Int64ToBytes$ (value As _Integer64)
    Dim s As String
    Dim temp As _Integer64
    temp = value
    s = String$(8, 0)

    Dim i As Integer
    For i = 8 To 1 Step -1
        Mid$(s, i, 1) = Chr$(temp And &HFF)
        temp = _ShR(temp, 8)
    Next i

    Int64ToBytes$ = s
End Function

' ------------------------------------------------------------------------------
' Helper: Converts a Hex string (returned by SHA1$) to a Binary String
' ------------------------------------------------------------------------------
Function HexToStr$ (hexStr As String)
    Dim s As String
    Dim i As Integer
    s = ""
    For i = 1 To Len(hexStr) Step 2
        s = s + Chr$(Val("&H" + Mid$(hexStr, i, 2)))
    Next i
    HexToStr$ = s
End Function

' ------------------------------------------------------------------------------
' Calculates Unix Timestamp (Seconds since 1970-01-01 00:00:00 UTC)
' Note: This uses the local computer time. TOTP requires accurate time.
' ------------------------------------------------------------------------------
Function GetUnixTime%%
    ' Use a known reference epoch
    ' QB64's TIMER is seconds since midnight.
    ' We need to calculate total days since 1970.

    Dim currDate As String, currTime As String
    currDate = Date$ ' MM-DD-YYYY
    currTime = Time$ ' HH:MM:SS

    Dim mm As Integer, dd As Integer, yyyy As Integer
    mm = Val(Left$(currDate, 2))
    dd = Val(Mid$(currDate, 4, 2))
    yyyy = Val(Right$(currDate, 4))

    ' Basic algorithm to convert date to days since epoch
    ' 1. Calculate days for full years
    Dim days As _Integer64
    Dim y As Integer
    days = 0
    For y = 1970 To yyyy - 1
        If (y Mod 4 = 0 And y Mod 100 <> 0) Or (y Mod 400 = 0) Then
            days = days + 366
        Else
            days = days + 365
        End If
    Next y

    ' 2. Calculate days for full months in current year
    Dim m As Integer
    Dim daysInMonth(12) As Integer
    daysInMonth(1) = 31: daysInMonth(2) = 28: daysInMonth(3) = 31
    daysInMonth(4) = 30: daysInMonth(5) = 31: daysInMonth(6) = 30
    daysInMonth(7) = 31: daysInMonth(8) = 31: daysInMonth(9) = 30
    daysInMonth(10) = 31: daysInMonth(11) = 30: daysInMonth(12) = 31

    ' Leap year check for current year
    If (yyyy Mod 4 = 0 And yyyy Mod 100 <> 0) Or (yyyy Mod 400 = 0) Then
        daysInMonth(2) = 29
    End If

    For m = 1 To mm - 1
        days = days + daysInMonth(m)
    Next m

    ' 3. Add current day
    days = days + (dd - 1)

    ' 4. Convert to seconds
    Dim totalSeconds As _Integer64
    totalSeconds = days * 86400

    ' 5. Add time from current day
    Dim hh As Integer, min As Integer, ss As Integer
    hh = Val(Left$(currTime, 2))
    min = Val(Mid$(currTime, 4, 2))
    ss = Val(Right$(currTime, 2))

    totalSeconds = totalSeconds + (hh * 3600) + (min * 60) + ss

    ' Note: DATE$ and TIME$ are local. TOTP technically requires UTC.
    ' If your PC is not in UTC, you might need to adjust the offset here.
    ' For simplicity, we assume the user's system clock is appropriate or
    ' they can adjust the mental offset.
    ' A robust solution would read time zone info from OS APIs, but that
    ' requires DECLARE LIBRARY which is complex for a snippet.
    ' We will add a manual timezone offset variable if needed, set to 0 for now.

    ' e.g. If you are EST (-5), you need to ADD 5 hours to get UTC.
    ' You can hardcode your offset here in seconds if needed.
    ' DIM tzOffsetSeconds AS _INTEGER64
    ' tzOffsetSeconds = 5 * 3600
    ' totalSeconds = totalSeconds + tzOffsetSeconds

    GetUnixTime%% = totalSeconds
End Function
The noticing will continue
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
Tongue Ubuntu 18.0.4 C++ Compilation Failed SOLVED (sorta) Shed_Grill 6 878 07-14-2025, 12:32 AM
Last Post: Shed_Grill

Forum Jump:


Users browsing this thread: 1 Guest(s)