TOTP / 2FA [All-OSs]

Share your advanced PureBasic knowledge/code with the community.
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

TOTP / 2FA [All-OSs]

Post by vwidmer »

Please improve on it if you can and post your changes.

totp.pbi

Code: Select all

XIncludeFile "hmac2.pb" ;https://www.purebasic.fr/english/viewtopic.php?f=12&t=66417
XIncludeFile "inc.base32.pbi"

ImportC "":time(*tloc = #Null):EndImport ;unix timestamp crossplattform 

Procedure.l Revese(*ptr,length=8) ;pack Quad
  *mem=AllocateMemory(length)
  CopyMemory(*ptr+3,*mem+4,1)
  CopyMemory(*ptr+2,*mem+5,1)
  CopyMemory(*ptr+1,*mem+6,1)
  CopyMemory(*ptr+0,*mem+7,1)  
  ProcedureReturn *mem
EndProcedure

Procedure.s GenerateOTP(secrect$,digits=6,interval=30)
  Protected OTP=0, modNumber=0, offset=0, FullOtp=0
  Protected Dim HashByteArray.a (0)  
  Protected secretbase32Hex$=base32Encode(secrect$,"hex") ;create base32 Hex String
  Protected SecretLen=Len(secretbase32Hex$)/2             ;size byte array
  Protected Dim Secret.a (0) 
  ReDim Secret(SecretLen)
  Hex2Dec(Secret(), secretbase32Hex$) ;create byte array from string
                                      ;ShowMemoryViewer(@Secret(),SecretLen)
  Protected timestamp=(time()/interval)
  Protected *timestamp=Revese(@timestamp)
  Protected signature$ = hmac_sha1binMod(@Secret(),SecretLen, *timestamp,8)
  ;ShowMemoryViewer(*timestamp,8)
  FreeMemory(*timestamp)
  ;Debug "signature$: " + signature$
  Protected len=Len(signature$)/2
  ReDim  HashByteArray(len)
  Hex2Dec(HashByteArray(), signature$)
  
  offset= HashByteArray(ArraySize(HashByteArray())-1) & $F
  FullOtp=(HashByteArray(offset) & $7f) * Pow(2, 24)
  FullOtp = FullOtp + (HashByteArray(offset + 1) & $ff) * Pow(2, 16)
  FullOtp = FullOtp + (HashByteArray(offset + 2) & $ff) * Pow(2, 8)
  FullOtp = FullOtp + (HashByteArray(offset + 3)  & $ff)
  modNumber = Pow(10, digits)
  OTP= FullOtp % modNumber
  ProcedureReturn RSet(Str(OTP), digits,"0")
EndProcedure

CompilerIf #PB_Compiler_IsMainFile
  Debug GenerateOTP("12345678901234567890")
CompilerEndIf
hmac2.pb

Code: Select all

;EnableExplicit
;    HMAC function implementation
;   2016         (c) Luna Sole

UseMD5Fingerprint() : UseSHA1Fingerprint() : UseSHA2Fingerprint() ; currently only verified with these algorithms


; convert hex string into raw bytes
; Out()      unsigned char array to receive result
; Hex$      string with hex data
; RETURN:   decimal value is placed to Out() array
Procedure Hex2Dec (Array Out.a (1), Hex$)
  Protected i2, max = Len(Hex$)
  ReDim Out((max + 1) / 2)
  For i2 = 1 To max Step 2
    Out(i2 / 2) = Val("$" + Mid(Hex$, i2, 2))
  Next i2
EndProcedure

; generates HMAC signature for specified message and key
; NOTE:         This function forces strings conversion to ASCII, both for key and message
;             I'm not sure how right to do that, but let it be for now (I don't want to do a painful debug of unicode version also ^^)
; PB_Cipher      what hashing to use (MD5, SHA1, SHA256 and some others)
; Message$      data to hash
; Key$         a very secret key
; RETURN:      string, representing HMAC hash
Procedure$ StringHMAC (PB_Cipher, Message$, Key$)
  
  #HMAC_BLOCKSIZE = 64 ; blocksize is 64 (bytes) when using one of the following hash functions: SHA-1, MD5, RIPEMD-128/160.
  
  ; First of all, convert key from string to binary
  ; If key is longer than block size, replace it with hash(key)
  Protected Dim key_bdata.a (#HMAC_BLOCKSIZE)
  If (StringByteLength(Key$, #PB_Ascii) > #HMAC_BLOCKSIZE)
    PokeS(@key_bdata(0), StringFingerprint(Key$, PB_Cipher), -1, #PB_Ascii | #PB_String_NoZero)
  Else
    PokeS(@key_bdata(0), Key$, -1, #PB_Ascii | #PB_String_NoZero)
  EndIf
  
  ; Now introduce o_key_pad/i_key_pad and XOR them with some magic numbers
  Protected Dim i_key_pad.a (0)
  Protected Dim o_key_pad.a (0)
  Protected Tmp
  CopyArray(key_bdata(), i_key_pad())
  CopyArray(key_bdata(), o_key_pad())
  For Tmp = 0 To #HMAC_BLOCKSIZE
    i_key_pad(Tmp) ! $36
    o_key_pad(Tmp) ! $5c
  Next Tmp
  
  ; At last, start hashing
  Protected Hash_i$, Hash_o$         ; there are two steps, those variables storing result for step 1 and 2
  Protected hHash                    ; handle to initiated hash routine
  Protected Dim TempRaw.a (0)        ; a temporary buffer for data transfer
  
  ; First, hash using i_key_pad() and data
  ReDim TempRaw(StringByteLength(Message$, #PB_Ascii))
  PokeS(@TempRaw(0), Message$, -1, #PB_Ascii | #PB_String_NoZero)
  hHash = StartFingerprint (#PB_Any, PB_Cipher)
  AddFingerprintBuffer (hHash, @i_key_pad(0), #HMAC_BLOCKSIZE)
  If ArraySize(TempRaw())
    AddFingerprintBuffer (hHash, @TempRaw(0), ArraySize(TempRaw()))
  EndIf
  Hash_i$ = FinishFingerprint(hHash)
  ; Finally, hash once more using o_key_pad() + result of previous hashing
  Hex2Dec(TempRaw(), Hash_i$)
  hHash = StartFingerprint (#PB_Any, PB_Cipher)
  AddFingerprintBuffer (hHash, @o_key_pad(0), #HMAC_BLOCKSIZE)
  AddFingerprintBuffer (hHash, @TempRaw(0), ArraySize(TempRaw()))
  Hash_o$ = FinishFingerprint(hHash)
  
  ProcedureReturn Hash_o$
EndProcedure

;Mod to handle binary data
Procedure.s hmac_sha1binMod(*SecretByteArray, SecretLen,*msg,msglen, blocksize.i=64, opad.a=$5C, ipad.a=$36) 
  Protected.i KeyLength, x
  Protected Result$, i_key$
  Protected *key, *o_key_pad, *i_key_pad, *i, *o
  
  KeyLength = SecretLen
  ;Debug "KeyLength: " + KeyLength
  If KeyLength > blocksize
    *key = AllocateMemory(KeyLength)
  ElseIf KeyLength < blocksize
    *key = AllocateMemory(blocksize)
  EndIf
  
  If *key
    CopyMemory(*SecretByteArray,*key,KeyLength)
    ;ShowMemoryViewer(*key,blocksize)
    *o_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
    If *o_key_pad
      For x = 0 To blocksize - 1
        PokeA(*o_key_pad + x, PeekA(*key + x) ! opad)
      Next x
      
      *i_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
      If *i_key_pad
        For x = 0 To blocksize - 1
          PokeA(*i_key_pad + x, PeekA(*key + x) ! ipad)
        Next x
        
        *i = AllocateMemory(blocksize + msglen)
        If *i
          CopyMemory(*i_key_pad, *i, blocksize)
          ;this work with bytes
          CopyMemory(*msg,*i + blocksize, msglen)
          ;ShowMemoryViewer(*msg,msglen)
          i_key$ = Fingerprint(*i, MemorySize(*i),#PB_Cipher_SHA1)
          FreeMemory(*i)
          
          *o = AllocateMemory(blocksize + 20)
          If *o
            CopyMemory(*o_key_pad, *o, blocksize)
            For x = 0 To 19
              PokeA(*o + blocksize + x, Val("$" + Mid(i_key$, x * 2 + 1, 2)))
            Next x
            
            Result$ = Fingerprint(*o, MemorySize(*o),#PB_Cipher_SHA1)
            FreeMemory(*o)
          EndIf
          
        EndIf
        FreeMemory(*i_key_pad)
      EndIf
      FreeMemory(*o_key_pad)
    EndIf
    FreeMemory(*key)
  EndIf
  ProcedureReturn Result$
EndProcedure

Procedure.s hmac_sha1(key$, msg$, blocksize.i=64, opad.a=$5C, ipad.a=$36)
  
  Protected.i KeyLength, x
  Protected Result$, i_key$
  Protected *key, *o_key_pad, *i_key_pad, *i, *o
  
  
  KeyLength = StringByteLength(key$, #PB_UTF8)
  If KeyLength > blocksize
    *key = AllocateMemory(KeyLength)
    If *key
      PokeS(*key, key$, -1, #PB_UTF8|#PB_String_NoZero)
      key$ = Fingerprint(*key, MemorySize(*key),#PB_Cipher_SHA1)
      FreeMemory(*key)
      KeyLength = StringByteLength(key$, #PB_UTF8)
      *key = AllocateMemory(KeyLength)
    EndIf
  ElseIf KeyLength < blocksize
    *key = AllocateMemory(blocksize)
  EndIf
  
  If *key
    PokeS(*key, key$, -1, #PB_UTF8|#PB_String_NoZero)
    
    *o_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
    If *o_key_pad
      For x = 0 To blocksize - 1
        PokeA(*o_key_pad + x, PeekA(*key + x) ! opad)
      Next x
      
      *i_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
      If *i_key_pad
        For x = 0 To blocksize - 1
          PokeA(*i_key_pad + x, PeekA(*key + x) ! ipad)
        Next x
        
        *i = AllocateMemory(blocksize + StringByteLength(msg$, #PB_UTF8))
        If *i
          CopyMemory(*i_key_pad, *i, blocksize)
          PokeS(*i + blocksize, msg$, -1, #PB_UTF8|#PB_String_NoZero)
          i_key$ = Fingerprint(*i, MemorySize(*i),#PB_Cipher_SHA1)
          FreeMemory(*i)
          
          *o = AllocateMemory(blocksize + 20)
          If *o
            CopyMemory(*o_key_pad, *o, blocksize)
            For x = 0 To 19
              PokeA(*o + blocksize + x, Val("$" + Mid(i_key$, x * 2 + 1, 2)))
            Next x
            
            Result$ = Fingerprint(*o, MemorySize(*o),#PB_Cipher_SHA1)
            FreeMemory(*o)
          EndIf
          
        EndIf
        FreeMemory(*i_key_pad)
      EndIf
      FreeMemory(*o_key_pad)
    EndIf
    FreeMemory(*key)
  EndIf
  
  ProcedureReturn Result$
  
EndProcedure




Procedure.s hmac_md5(key$, msg$, blocksize.i=64, opad.a=$5C, ipad.a=$36)
  
  Protected.i KeyLength, x
  Protected Result$, i_key$
  Protected *key, *o_key_pad, *i_key_pad, *i, *o
  
  
  KeyLength = StringByteLength(key$, #PB_UTF8)
  If KeyLength > blocksize
    *key = AllocateMemory(KeyLength)
    If *key
      PokeS(*key, key$, -1, #PB_UTF8|#PB_String_NoZero)
      key$ = Fingerprint(*key, MemorySize(*key),#PB_Cipher_MD5)
      FreeMemory(*key)
      KeyLength = StringByteLength(key$, #PB_UTF8)
      *key = AllocateMemory(KeyLength)
    EndIf
  ElseIf KeyLength < blocksize
    *key = AllocateMemory(blocksize)
  EndIf
  
  If *key
    PokeS(*key, key$, -1, #PB_UTF8|#PB_String_NoZero)
    
    *o_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
    If *o_key_pad
      For x = 0 To blocksize - 1
        PokeA(*o_key_pad + x, PeekA(*key + x) ! opad)
      Next x
      
      *i_key_pad = AllocateMemory(blocksize, #PB_Memory_NoClear)
      If *i_key_pad
        For x = 0 To blocksize - 1
          PokeA(*i_key_pad + x, PeekA(*key + x) ! ipad)
        Next x
        
        *i = AllocateMemory(blocksize + StringByteLength(msg$, #PB_UTF8))
        If *i
          CopyMemory(*i_key_pad, *i, blocksize)
          PokeS(*i + blocksize, msg$, -1, #PB_UTF8|#PB_String_NoZero)
          i_key$ = Fingerprint(*i, MemorySize(*i),#PB_Cipher_MD5)
          FreeMemory(*i)
          
          *o = AllocateMemory(blocksize + 16)
          If *o
            CopyMemory(*o_key_pad, *o, blocksize)
            For x = 0 To 15
              PokeA(*o + blocksize + x, Val("$" + Mid(i_key$, x * 2 + 1, 2)))
            Next x
            
            Result$ = Fingerprint(*o, MemorySize(*o),#PB_Cipher_MD5)
            FreeMemory(*o)
          EndIf
          
        EndIf
        FreeMemory(*i_key_pad)
      EndIf
      FreeMemory(*o_key_pad)
    EndIf
    FreeMemory(*key)
  EndIf
  
  ProcedureReturn Result$
  
EndProcedure
inc.base32.pbi

Code: Select all

Procedure.s base32_decode(in1.s)
  keyStr$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="
  For i = 1 To Len(in1)
   vvll=FindString(keyStr$,Mid(in1,i,1))-1
    If vvll >= 0 And vvll < 32
      buffer << 5;
      buffer | vvll;
      bitsLeft + 5;
      If bitsLeft >= 8
        dStr$ + Chr((buffer >> (bitsLeft - 8)) & 255); + $FF;
        bitsLeft - 8                                    
      EndIf            
    EndIf      
  Next   
  ProcedureReturn dStr$
EndProcedure

Procedure.s base32Encode(base32$,outputFormat$="bin") ;encode base32 string; outputFormat$ bin/hex
  
  Protected key$="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", output$=""
  Protected i=0, buffer=0, bitsLeft=0
  
  While i < Len(base32$)
    val=FindString(key$,Mid(base32$,i+1,1))-1
    If  val>=0 And val<32
      buffer= (buffer << 5 ) | val
      bitsLeft=bitsLeft+5
      If  bitsLeft>=8
        If outputFormat$="hex"
          output$=output$ + RSet(Hex((buffer >> (bitsLeft - 8)) & $FF),2,"0")
        Else
          output$=output$ + Chr((buffer >> (bitsLeft - 8)) & $FF)
        EndIf
        bitsLeft=bitsLeft-8
      EndIf 
    EndIf 
    i=i+1
  Wend
  
  If bitsLeft>0
    buffer=buffer<<5
    If outputFormat$="hex"
      output$=output$ + RSet(Hex((buffer >> (bitsLeft - 3)) & $FF),2,"0")
    Else
      output$=output$ + Chr((buffer >> (bitsLeft - 3)) & $FF)
    EndIf
  EndIf
  
  ProcedureReturn output$
EndProcedure
I think this should work on all OSs if not let me know only tested on Linux.
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: TOTP / 2FA [All-OSs]

Post by Kwai chang caine »

Hello vwidmer
Works in W7 X86 / v5.70 X86
Return a number of 6 characters, obviously never the same at each run
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
Post Reply