HMAC.pbi for many ciphers

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

HMAC.pbi for many ciphers

Post by infratec »

I reworked my HMAC.pbi file.

Before it was only possible to use strings as secret and key.
Now I wrote a version with buffers and a wrapper for using strings.

Because I needed secrets and messages with binary data and including zeroes.
Stay tuned :wink:

Code: Select all

;
; https://en.wikipedia.org/wiki/HMAC
;
; HMAC(M) = H((K XOR opad) + H((K XOR ipad) + M))
;
; M    = message
; K    = key
; opad = 0x5C
; ipad = 0x36
; H    = hash function
;

; https://www.purebasic.fr/english/viewtopic.php?t=85009

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf


Macro UseMD2Fingerprint()
  XIncludeFile "../hash/MD2.pbi"
EndMacro

Macro UseMD4Fingerprint()
  XIncludeFile "../hash/MD4.pbi"
EndMacro


CompilerIf #PB_Compiler_IsMainFile
  UseMD2Fingerprint()
  UseMD4Fingerprint()
  UseMD5Fingerprint()
  UseSHA1Fingerprint()
  UseSHA2Fingerprint()
  UseSHA3Fingerprint()
CompilerEndIf




Procedure.s FingerprintMacro(*Buffer, Size.i, Plugin.i, Bits.i=254)
  
  Protected Hash$
  
  
  Select Plugin
    Case -1
      CompilerIf Defined(PB_Cipher_MD2, #PB_Constant)
      Case #PB_Cipher_MD2
        Hash$ = FingerprintMD2(*Buffer, Size)
      CompilerEndIf
      CompilerIf Defined(PB_Cipher_MD4, #PB_Constant)
      Case #PB_Cipher_MD4
        Hash$ = FingerprintMD4(*Buffer, Size)
      CompilerEndIf
    Default
      Hash$ = Fingerprint(*Buffer, Size, Plugin, Bits)
  EndSelect
  
  ProcedureReturn Hash$
  
EndProcedure

Macro Fingerprint(Buffer, Size, Plugin, Bits)
  FingerprintMacro(Buffer, Size, Plugin, Bits)
EndMacro

Procedure.s StringFingerprintMacro(String.s, Plugin.i, Bits.i=0, Format.i=#PB_UTF8)
  
  Protected Hash$
  
  
  Select Plugin
    Case -1
      CompilerIf Defined(PB_Cipher_MD2, #PB_Constant)
      Case #PB_Cipher_MD2
        Hash$ = StringFingerprintMD2(String)
      CompilerEndIf
      CompilerIf Defined(PB_Cipher_MD4, #PB_Constant)
      Case #PB_Cipher_MD4
        Hash$ = StringFingerprintMD4(String)
      CompilerEndIf
    Default
      Hash$ = StringFingerprint(String, Plugin, Bits, Format)
  EndSelect
  
  ProcedureReturn Hash$
  
EndProcedure


Macro StringFingerprint(String, Plugin, Bits, Format)
  StringFingerprintMacro(String, Plugin, Bits, Format)
EndMacro




Procedure HMAC_HexStringToBin(*HexString.Ascii, *Destination.Ascii)
  
  Protected HighNibble, LowNibble
  
  
  If *HexString And *Destination
    While *HexString\a
      
      HighNibble = *HexString\a - '0'
      If HighNibble > $f
        HighNibble - $27
      EndIf
      *HexString + 2
      
      LowNibble = *HexString\a - '0'
      If LowNibble > $f
        LowNibble - $27
      EndIf
      *HexString + 2
      
      *Destination\a = HighNibble << 4 | LowNibble
      *Destination + 1
      ;Debug Hex(HighNibble << 4 | LowNibble, #PB_Ascii)
    Wend
  EndIf
  
EndProcedure


Procedure.s HMAC(*msg, *key, cipher.i=#PB_Cipher_SHA1, bits.i=256, encode$="Hex")
  
  #IPAD = $36
  #OPAD = $5C
  
  
  Protected.i i, BlockSize, cipherResultSize, MsgSize, KeySize
  Protected result$, innerHash$, key$
  Protected *tmp, *ptr.Ascii, *innerHash, *outerHash, *HMAC_key
  
  
  ; adjust the needed values for different ciphers
  BlockSize = 64
  Select cipher
      CompilerIf Defined(PB_Cipher_MD2, #PB_Constant)
      Case #PB_Cipher_MD2
        BlockSize = 16
        cipherResultSize = 16
      CompilerEndIf
      
      CompilerIf Defined(PB_Cipher_MD4, #PB_Constant)
      Case #PB_Cipher_MD4
        cipherResultSize = 16
      CompilerEndIf
      
    Case #PB_Cipher_MD5
      cipherResultSize = 16
    Case #PB_Cipher_SHA1
      cipherResultSize = 20
    Case #PB_Cipher_SHA2
      If bits > 256
        BlockSize = 128
      EndIf
      cipherResultSize = bits / 8
    Case #PB_Cipher_SHA3
      Select bits
        Case 224
          BlockSize = 1152 / 8
        Case 256
          BlockSize = 1088 / 8
        Case 384
          BlockSize = 832 / 8
        Case 512
          BlockSize = 576 / 8
      EndSelect
      cipherResultSize = bits / 8
    Default
      ProcedureReturn "cipher not implemented"
  EndSelect
  
  ; special rule if length of the key is larger then the blocksize:
  ; use H(K) instead of K
  
  If *key
    KeySize = MemorySize(*key)
  EndIf
  
  If KeySize > BlockSize
    key$ = Fingerprint(*key, KeySize, cipher, bits)
    *HMAC_key = AllocateMemory(cipherResultSize)
    If *HMAC_key
      HMAC_HexStringToBin(@key$, *HMAC_key)
    EndIf
  Else
    *HMAC_key = AllocateMemory(blocksize)
    If *HMAC_key
      If *key
        CopyMemory(*key, *HMAC_key, KeySize)
      EndIf
    EndIf
  EndIf
  
  *outerHash = AllocateMemory(BlockSize + cipherResultSize)
  If *outerHash
    
    ; K XOR opad
    If *key
      CopyMemory(*HMAC_key, *outerHash, MemorySize(*HMAC_key))
    EndIf
    *ptr = *outerHash
    For i = 0 To BlockSize - 1
      *ptr\a = *ptr\a ! #OPAD
      *ptr + 1
    Next i
    
    If *msg
      MsgSize = MemorySize(*msg)
    EndIf
    
    *innerHash = AllocateMemory(BlockSize + MsgSize)
    If *innerHash
      
      ; K XOR ipad
      *ptr = *innerHash
      CopyMemory(*HMAC_key, *innerHash, MemorySize(*HMAC_key))
      For i = 0 To BlockSize - 1
        *ptr\a = *ptr\a ! #IPAD
        *ptr + 1
      Next i
      
      ; (K XOR ipad) + M)
      If *msg
        CopyMemory(*msg, *ptr, MsgSize)
      EndIf
      
      ; H((K XOR ipad) + M))
      innerHash$ = Fingerprint(*innerHash, MemorySize(*innerHash), cipher, bits)
      
      ; (K XOr opad) + H((K XOr ipad) + M)
      HMAC_HexStringToBin(@innerHash$, *outerHash + BlockSize)
      
      ; H((K XOR opad) + H((K XOR ipad) + M))
      result$ = Fingerprint(*outerHash, BlockSize + cipherResultSize, cipher, bits)
      
      ; optional result is coded in Base64
      If LCase(encode$) = "base64"
        *tmp = AllocateMemory(cipherResultSize)
        If *tmp
          HMAC_HexStringToBin(@result$, *tmp)
          result$ = Base64Encoder(*tmp, MemorySize(*tmp))
          FreeMemory(*tmp)
        EndIf
      EndIf
      
      FreeMemory(*innerHash)
    EndIf
    
    FreeMemory(*outerHash)
  EndIf
  
  ProcedureReturn result$
  
EndProcedure


Procedure.s StringHMAC(msg$, key$, cipher.i=#PB_Cipher_SHA1, bits.i=256, encode$="Hex")
  
  Protected *msg, *key, HMAC$
  
  
  If msg$ <> ""  
    *msg = AllocateMemory(StringByteLength(msg$, #PB_UTF8), #PB_Memory_NoClear)
    PokeS(*msg, msg$, -1, #PB_UTF8|#PB_String_NoZero)
  EndIf
  
  If key$ <> ""
    *key = AllocateMemory(StringByteLength(key$, #PB_UTF8), #PB_Memory_NoClear)
    PokeS(*key, key$, -1, #PB_UTF8|#PB_String_NoZero)
  EndIf
  
  HMAC$ = HMAC(*msg, *key, cipher, bits, encode$)
  
  If *key : FreeMemory(*key) : EndIf
    
  If *msg : FreeMemory(*msg) : EndIf
  
  ProcedureReturn HMAC$
  
EndProcedure



CompilerIf #PB_Compiler_IsMainFile
  ;-Demo
  Define key$, msg$
  
  
  msg$ = ""
  key$ = ""
  Debug "msg:"
  Debug "key:"
  Debug "MD5"
  Debug "Calc     : " + StringHMAC(msg$, key$, #PB_Cipher_MD5)
  Debug "Should be: 74e6f7298a9c2d168935f58c001bad88"
  
  Debug ""
  Debug "msg:"
  Debug "key:"
  Debug "SHA-1"
  Debug "Calc     : " + StringHMAC(msg$, key$)
  Debug "Should be: fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"
  
  msg$ = "test string"
  key$ = "testkey123"
  Debug ""
  Debug "Hex   : " + StringHMAC(msg$, key$)
  Debug "Base64: " + StringHMAC(msg$, key$, #PB_Cipher_SHA1, 0, "Base64")
  Debug "PHP   : XosSbTmfw5OJ4mh1NSupUH3l7HY="
  
  ; Example from:
  ; https://developer.twitter.com/en/docs/authentication/oauth-1-0a/creating-a-signature
  
  msg$ = "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521"
  key$ = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"
  
  Debug ""
  Debug "twitter  : " + StringHMAC(msg$, key$)
  Debug "Should be: 842b5299887e88760212a056ac4ec2ee1626b549"
  
  
  ; following tests are verified by
  ; (https://www.freeformatter.com/hmac-generator.html) no SHA-3 calculations
  ; https://appdevtools.com/hmac-generator              generates false SHA-3 !!!!!!
  ; https://www.liavaag.org/English/SHA-Generator/HMAC/
  ; https://wtools.io/generate-hmac-hash
  
  Debug ""
  Debug "test For non ASCII:"
  msg$ = "Begrüßung"
  key$ = "123"
  
  Debug "msg: " + msg$
  Debug "key: " + key$
  
  Debug ""
  Debug "SHA1       " + StringHMAC(msg$, key$)
  Debug "Should be: 149b6e8402e9526bbcbc93dc99b0fea3b65e25b0"
  
  
  msg$ = "PureBasic"
  key$ = "123456"
  
  Debug ""
  Debug "All ciphers tests:"
  Debug "msg: " + msg$
  Debug "key: " + key$
  
  Debug ""
  Debug "MD5        " + StringHMAC(msg$, key$, #PB_Cipher_MD5)
  Debug "Should be: bc2c0b90c3b8b46ebdab6a188ce7546c"
  
  Debug ""
  Debug "SHA1       " + StringHMAC(msg$, key$, #PB_Cipher_SHA1)
  Debug "Should be: 9fa85352d7a125c06f1c0f4653633ee482b9e216"
  
  Debug ""
  Debug "SHA2 224   " + StringHMAC(msg$, key$, #PB_Cipher_SHA2, 224)
  Debug "Should be: b230ab71aca84befc565276112a3c16356cbdbd70c465080e1c7ee89"
  
  Debug ""
  Debug "SHA2 256   " + StringHMAC(msg$, key$, #PB_Cipher_SHA2, 256)
  Debug "Should be: 27d722c54c40d9b9b4e15db1e5da439b493f22872d469b7bc60ab73d05d7a486"
  
  Debug ""
  Debug "SHA2 384   " + StringHMAC(msg$, key$, #PB_Cipher_SHA2, 384)
  Debug "Should be: c348fe1e3a4acfd2d180feaca699145100f0f6a0bc02472ee775b250a99a5dcca947496c86ae9ed62cda320f99e7bc54"
  
  Debug ""
  Debug "SHA2 512   " + StringHMAC(msg$, key$, #PB_Cipher_SHA2, 512)
  Debug "Should be: cc0d17134c476566e6d19d9bb6f08ca127209b711cf1669d3169541d038de1dae9efd031f473b8f502326ee1aec1612dc2309da00f49c163746e4e1f85e305d1"
  
  Debug ""
  Debug "SHA3 224   " + StringHMAC(msg$, key$, #PB_Cipher_SHA3, 224)
  Debug "Should be: 8a3e3c00fa9ecec23c8e14336936ea6b62b81e2742ac9c171e931a27"
  
  Debug ""
  Debug "SHA3 256   " + StringHMAC(msg$, key$, #PB_Cipher_SHA3, 256)
  Debug "Should be: 56d40211ecbf9825d2545fadb2768aed781fe9bd0cbf33e874e2308e430241b7"
  
  Debug ""
  Debug "SHA3 384   " + StringHMAC(msg$, key$, #PB_Cipher_SHA3, 384)
  Debug "Should be: af025b8dea23ee1db0ceaa60904ec28778a6e3a8c1d12924cf63c952a0689cc98e1df1cb448c2c7b7544cc33a26e3632"
  
  Debug ""
  Debug "SHA3 512   " + StringHMAC(msg$, key$, #PB_Cipher_SHA3, 512)
  Debug "Should be: 7a46e0efc0fe813c992f2e86a70c304cc65b590e0cc6f3870936e0b96cf39d4aa98a480e66aae359d72f851c133e6b6a53fe06a1f7ebcd02186ab65c06f9a9fb"
  
  CompilerIf Defined(PB_Cipher_MD2, #PB_Constant)
    Debug ""
    Debug "MD2        " + StringHMAC(msg$, key$, #PB_Cipher_MD2)
    Debug "Should be: 7adf32a888e55935bf4fccd0c4c97230"
  CompilerEndIf
  
  CompilerIf Defined(PB_Cipher_MD4, #PB_Constant)
    Debug ""
    Debug "MD4        " + StringHMAC(msg$, key$, #PB_Cipher_MD4)
    Debug "Should be: c93dd0d75e2d46057893f4082854fd13"
  CompilerEndIf
  
CompilerEndIf
MD2.pbi:
https://www.purebasic.fr/english/viewtopic.php?t=81309

MD4.pbi:
https://www.purebasic.fr/english/viewtopic.php?t=81310
Last edited by infratec on Mon Aug 05, 2024 7:09 am, edited 1 time in total.
User avatar
idle
Always Here
Always Here
Posts: 5834
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: HMAC.pbi for many ciphers

Post by idle »

Thanks that will be very useful
1988reload
New User
New User
Posts: 6
Joined: Mon Jul 08, 2024 1:57 pm

Re: HMAC.pbi for many ciphers

Post by 1988reload »

thanks, works fine.

is there a way to calculate the result step by step and not all at once? so for example instead of using 100% buffer, calculate the first 50% and then the last 50% after that to reduce memory usage (similar to what AddFingerprintBuffer is doing)?
Post Reply