hmac_sha1

Share your advanced PureBasic knowledge/code with the community.
hss
User
User
Posts: 69
Joined: Thu Mar 08, 2007 6:02 pm

hmac_sha1

Post by hss »

PB implantation of HMAC_SHA1;
useful for authentication signing - e.g. Twitter's OAuth
http://en.wikipedia.org/wiki/HMAC

Code: Select all

; hss/2011

Procedure.s hmac_sha1(key.s,msg.s) 
  
  If(Len(key)>64)
    key=SHA1Fingerprint(@key,Len(key))
    For x=1 To 20:PokeB(@key+x-1,Val("$"+Mid(key,(x*2)-1,2))):Next
    For x=20 To Len(key):PokeB(@key+x,0):Next
  EndIf
  
  
key=LSet(key,64,Chr(0)):ipad.s=LSet(ipad,64,Chr($36)):opad.s=LSet(opad,64,Chr($5c))
*ipadt=AllocateMemory(64+Len(msg)):*opadt=AllocateMemory(64+20)
PokeS(*ipadt,ipad,64):PokeS(*opadt,opad,64)

For x=1 To 64
 PokeB(*opadt+x-1,Asc(Mid(opad,x,1))!Asc(Mid(key,x,1)))
 PokeB(*ipadt+x-1,Asc(Mid(ipad,x,1))!Asc(Mid(key,x,1)))
Next

PokeS(*ipadt+64,msg,Len(msg))
one.s=SHA1Fingerprint(*ipadt,MemorySize(*ipadt))
For x=64 To 84:xx+1:PokeB(*opadt+x,Val("$"+Mid(one,(xx*2)-1,2))):Next
two.s=SHA1Fingerprint(*opadt,64+20)

*i=AllocateMemory(20)
For x=1 To 20:PokeB(*i+x-1,Val("$"+Mid(two,(x*2)-1,2))):Next

*o=AllocateMemory(20*1.35)
Base64Encoder(*i,20,*o,20*1.35)

r.s=PeekS(*o)

r=ReplaceString(r,"+","%2B")
r=ReplaceString(r,"=","%3D")
r=ReplaceString(r,"/","%2F")

ProcedureReturn r

EndProcedure

JS testing - http://jssha.sourceforge.net/ (Debug two.s in PB to compare results)

PHP

Code: Select all

echo rawurlencode(base64_encode(hash_hmac('sha1',$msg,$key,TRUE)));
giammy
User
User
Posts: 16
Joined: Wed Aug 03, 2011 10:43 am

Re: hmac_sha1

Post by giammy »

Great job! :wink:

I am a newbie
I need for an hmac_sha256 implementation in Purebasic

can anyone help me?

Thanks in advance!
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: hmac_sha1

Post by Seymour Clufley »

Trying to update this code to work with PB6.01, and I've run into trouble.

Code: Select all

Procedure.s hmac_sha1(key.s,msg.s) 
  UseSHA1Fingerprint()
  
  If(Len(key)>64)
    key = StringFingerprint(key,#PB_Cipher_SHA1)
    For x=1 To 20:PokeB(@key+x-1,Val("$"+Mid(key,(x*2)-1,2))):Next
    For x=20 To Len(key):PokeB(@key+x,0):Next
  EndIf
  
  key = LSet(key,64,Chr(0))
  ipad.s = LSet(ipad,64,Chr($36))
  opad.s = LSet(opad,64,Chr($5c))
  *ipadt = AllocateMemory(64+Len(msg))
  PokeS(*ipadt,ipad,64)
  *opadt = AllocateMemory(64+20)
  PokeS(*opadt,opad,64)
  
  For x=1 To 64
    PokeB(*opadt+x-1,Asc(Mid(opad,x,1))!Asc(Mid(key,x,1)))
    PokeB(*ipadt+x-1,Asc(Mid(ipad,x,1))!Asc(Mid(key,x,1)))
  Next
  
  PokeS(*ipadt+64,msg,Len(msg))
  ipadt.s = PeekS(*ipadt,MemorySize(*ipadt))
  one.s = StringFingerprint(ipadt,#PB_Cipher_SHA1)
  For x=64 To 84:xx+1:PokeB(*opadt+x,Val("$"+Mid(one,(xx*2)-1,2))):Next
  opadt.s = PeekS(*opadt,64+20)
  two.s = StringFingerprint(opadt,#PB_Cipher_SHA1)
  
  *i = AllocateMemory(20)
  For x = 1 To 20
    PokeB(*i+x-1,Val("$"+Mid(two,(x*2)-1,2)))
  Next
  
  r.s = Base64Encoder(*i,20)
  
  r=ReplaceString(r,"+","%2B")
  r=ReplaceString(r,"=","%3D")
  r=ReplaceString(r,"/","%2F")
  
  ProcedureReturn r
  
EndProcedure

key.s = "testkey123"
msg.s = "test string"
sha.s = hmac_sha1(key,msg)
Debug URLEncoder(sha)
Debug "XosSbTmfw5OJ4mh1NSupUH3l7HY=" ; what the PHP version outputs
Can anyone see where I'm going wrong?
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

Was a problem between unicode and ascii.
But it was to complicated to change this, so I implemented it from scratch:

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?p=599038#p599038

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf


Procedure StringHMAC_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 StringHMAC(msg$, key$, cipher.i=#PB_Cipher_SHA1, bits.i=256, encode$="Hex")
  
  #IPAD = $36
  #OPAD = $5C
  
  
  Protected.i i, BlockSize, cipherResultSize
  Protected result$, innerHash$
  Protected *tmp, *ptr.Ascii, *innerHash, *outerHash
  
  
  ; adjust the needed values for different ciphers
  BlockSize = 64
  Select cipher
    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 StringByteLength(key$, #PB_Ascii) > BlockSize
    key$ = StringFingerprint(key$, cipher, bits, #PB_Ascii)
    *tmp = AllocateMemory(cipherResultSize)
    If *tmp
      StringHMAC_HexStringToBin(@key$, *tmp)
      key$ = PeekS(*tmp, cipherResultSize, #PB_Ascii)
      FreeMemory(*tmp)
    EndIf
  EndIf
  
  
  *outerHash = AllocateMemory(BlockSize + cipherResultSize)
  If *outerHash
    
    ; K XOR opad
    *ptr = *outerHash
    PokeS(*outerHash, key$, -1, #PB_Ascii|#PB_String_NoZero)
    For i = 0 To BlockSize - 1
      *ptr\a = *ptr\a ! #OPAD
      *ptr + 1
    Next i
      
    *innerHash = AllocateMemory(BlockSize + StringByteLength(msg$, #PB_UTF8))
    If *innerHash
      
      ; K XOR ipad
      *ptr = *innerHash
      PokeS(*innerHash, key$, -1, #PB_Ascii|#PB_String_NoZero)
      For i = 0 To BlockSize - 1
        *ptr\a = *ptr\a ! #IPAD
        *ptr + 1
      Next i
      
      ; (K XOR ipad) + M)
      PokeS(*ptr, msg$, -1, #PB_UTF8|#PB_String_NoZero)
      
      ; H((K XOR ipad) + M))
      innerHash$ = Fingerprint(*innerHash, MemorySize(*innerHash), cipher, bits)
      
      ; (K XOr opad) + H((K XOr ipad) + M)
      StringHMAC_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
          StringHMAC_HexStringToBin(@result$, *tmp)
          result$ = Base64Encoder(*tmp, MemorySize(*tmp))
          FreeMemory(*tmp)
        EndIf
      EndIf
      
      FreeMemory(*innerHash)
    EndIf
    
    FreeMemory(*outerHash)
  EndIf
  
  ProcedureReturn result$
  
EndProcedure




CompilerIf #PB_Compiler_IsMainFile
  
  Define key$, msg$
  
  
  UseMD5Fingerprint()
  UseSHA1Fingerprint()
  UseSHA2Fingerprint()
  UseSHA3Fingerprint()
  
  
  msg$ = "test string"
  key$ = "testkey123"
  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"
  
CompilerEndIf
Last edited by infratec on Sun Apr 09, 2023 8:35 pm, edited 16 times in total.
User avatar
mk-soft
Always Here
Always Here
Posts: 6202
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: hmac_sha1

Post by mk-soft »

PB Strings are Unicode (2 bytes)
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Fred
Administrator
Administrator
Posts: 18153
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: hmac_sha1

Post by Fred »

Cool stuff
User avatar
blueb
Addict
Addict
Posts: 1111
Joined: Sat Apr 26, 2003 2:15 pm
Location: Cuernavaca, Mexico

Re: hmac_sha1

Post by blueb »

Lunasole's excellent code (with Rinzwind's #PB_UTF8 mods) has worked well for me.
(using Win 11 Pro)

Includes MD5 / SHA1 / SHA2

see: viewtopic.php?p=493022#p493022

Code: Select all

; =================================================================
;
; Author:    Luna Sole    
; Date:       August 19th, 2016
; Explain:    HMAC function implementation (hash-based message authentication code)
;
; In cryptography, a keyed-hash message authentication code (HMAC) is a specific type of message 
; authentication code (MAC) involving a cryptographic hash function (hence the 'H') in combination 
; with a secret cryptographic key. As with any MAC, it may be used to simultaneously verify both 
; the data integrity and the authentication of a message. Any cryptographic hash function, such 
; as MD5 or SHA-1, may be used in the calculation of an HMAC; the resulting MAC algorithm is termed 
; HMAC-MD5 or HMAC-SHA1 accordingly. The cryptographic strength of the HMAC depends upon the 
; cryptographic strength of the underlying hash function, the size of its hash output, and on the 
; size and quality of the key.
; An iterative hash function breaks up a message into blocks of a fixed size and iterates over them 
; with a compression function. For example, MD5 and SHA-1 operate on 512-bit blocks. The size of the
; output of HMAC is the same as that of the underlying hash function (128 or 160 bits in the case of 
; MD5 or SHA-1, respectively), although it can be truncated if desired.
;
; Typical Usage:  HMAC Sample.pb        
; =================================================================
; Modifications Feb 2020 by Rizwind
; You might want to use #PB_UTF8 as encoding to make it compatible with other generator results when using unicode strings.
; Replaced all #PB_ASCII constants with #PB_UTF8
; Results for ascii values will stay the same since utf-8 is backward compatible.
;	'random' test text: Begrüßenภาษาไทย!
;	SHA2: 7596eaf61caf36289d5b1c9c8780dc657767e867a00d079bd9da518bb254a6c3
;  see the bottom for example
;	Another example of why PB should include hash functions that return raw byte data.
; ------------------------------------------------------------------------------------------------
EnableExplicit


Procedure Hex2Dec (Array Out.a (1), Hex$) ; 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

   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
; -------------------------------------------------
Procedure$ StringHMAC (PB_Cipher, Message$, Key$) ; 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:  - a string representing HMAC hash
   ; --------------------------------------------------------
   UseMD5Fingerprint() : UseSHA1Fingerprint() : UseSHA2Fingerprint() ; currently only verified with these algorithms
   #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_UTF8) > #HMAC_BLOCKSIZE)
      PokeS(@key_bdata(0), StringFingerprint(Key$, PB_Cipher), -1, #PB_UTF8 | #PB_String_NoZero)
   Else
      PokeS(@key_bdata(0), Key$, -1, #PB_UTF8 | #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_UTF8))
   PokeS(@TempRaw(0), Message$, -1, #PB_UTF8 | #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


; some tests
Debug "* HMAC of empty data and empty key *"
Debug "MD5:   " + StringHMAC(#PB_Cipher_MD5, "", "")  ; MD5 = 74e6f7298a9c2d168935f58c001bad88
Debug "SHA1:  " + StringHMAC(#PB_Cipher_SHA1, "", "") ; SHA1 = fbdb1d1b18aa6c08324b7d64b71fb76370690e1d
Debug "SHA2:  " + StringHMAC(#PB_Cipher_SHA2, "", "") ; SHA256 = b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad
Debug ""

Debug "* HMAC of control data to check is it OK *"
Debug "MD5:   " + StringHMAC(#PB_Cipher_MD5,  "The quick brown fox jumps over the lazy dog", "key") ; MD5 = 80070713463e7749b90c2dc24911e275
Debug "SHA1:  " + StringHMAC(#PB_Cipher_SHA1, "The quick brown fox jumps over the lazy dog", "key")   ; SHA1 = de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
Debug "SHA2:  " + StringHMAC(#PB_Cipher_SHA2, "The quick brown fox jumps over the lazy dog", "key")   ; SHA256 = f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
Debug ""
;	'random' test text: Begrüßenภาษาไทย! (Rinzwind sample)
Debug "Rinzwind's sample... SHA2:  " + StringHMAC(#PB_Cipher_SHA2, "Begrüßenภาษาไทย!", "key") ;	SHA2: 7596eaf61caf36289d5b1c9c8780dc657767e867a00d079bd9da518bb254a6c3
Debug "                 Expected:  7596eaf61caf36289d5b1c9c8780dc657767e867a00d079bd9da518bb254a6c3"
Debug ""
Debug "- Done -"

; Control data and resulting HMAC-hashes for it taken from wiki page:
;     https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Examples

- It was too lonely at the top.

System : PB 6.21(x64) and Win 11 Pro (x64)
Hardware: AMD Ryzen 9 5900X w/64 gigs Ram, AMD RX 6950 XT Graphics w/16gigs Mem
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: hmac_sha1

Post by Seymour Clufley »

infratec wrote: Sat Apr 08, 2023 12:13 pm Was a problem between unicode and ascii.
But it was to complicated to change this, so I implemented it from scratch
Thank you very much!
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: hmac_sha1

Post by Seymour Clufley »

Infratec's algorithm works as expected for the test string above and for the Wikipedia test string, but not for this Twitter test string. That page says what the HMAC_SHA1 should produce, and that is outputted by the PHP's algorithm, but not by Infratec's algorithm.

Code: Select all

Procedure.s HMAC_SHA1(msg.s, key.s, outputFormat.s)
  
  #IPAD = $36
  #OPAD = $5C
  
  UseSHA1Fingerprint()
  
  Protected.i i
  Protected.s r, inner
  Protected *ipad, *opad, *r, *buffer
  
  
  If Len(key) > 64
    key = StringFingerprint(key, #PB_Cipher_SHA1)
  EndIf
  
  *opad = AllocateMemory(64)
  If *opad
    PokeS(*opad, key, -1, #PB_Ascii|#PB_String_NoZero)
    For i = 0 To 63
      PokeA(*opad + i, PeekA(*opad + i) ! #OPAD)
    Next i
    
    *ipad = AllocateMemory(64)
    If *ipad
      PokeS(*ipad, key, -1, #PB_Ascii|#PB_String_NoZero)
      For i = 0 To 63
        PokeA(*ipad + i, PeekA(*ipad + i) ! #IPAD)
      Next i
      
      *buffer = AllocateMemory(64 + 20)
      If *buffer
        CopyMemory(*opad, *buffer, 64)
        
        inner = StringFingerprint(PeekS(*ipad, 64, #PB_Ascii) + msg, #PB_Cipher_SHA1)
        For i = 0 To 19
          PokeA(*buffer + 64 + i, Val("$" + Mid(inner, i * 2 + 1, 2)))
        Next i
        
        r = Fingerprint(*buffer, 84, #PB_Cipher_SHA1)
        
        If LCase(outputFormat) = "base64"
          *r = AllocateMemory(20)
          If *r
            For i = 0 To 19
              PokeA(*r + i, Val("$" + Mid(r, i * 2 + 1, 2)))
            Next i
            r = Base64Encoder(*r, MemorySize(*r))
            FreeMemory(*r)
          EndIf
          
        EndIf
        
        FreeMemory(*buffer)
      EndIf
      
      FreeMemory(*ipad)
    EndIf
    
    FreeMemory(*opad)
  EndIf
  
  ProcedureReturn r
  
EndProcedure


signatureBaseString.s = "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"
signingKey.s = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"
Debug "hCtSmYh+iHYCEqBWrE7C7hYmtUk=" ; this is what it should be, according to Twitter
Debug HMAC_SHA1(signatureBaseString,signingKey,"Base64")
This cipher stuff is all beyond my expertise, so I hope someone else (Infratec?) can work out what is going on here.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

Fixed my code above:
viewtopic.php?p=599038#p599038

As expected, it was a problem when the key was longer then the blocksize.
But lunasoles code looks better then mine.
Unfortunately he also has a bug inside.

I will try to fix this too.
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

Changed the listing above:

Now also supports non ASCII msgs and other ciphers.
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

I fixed lunasoles stuff, but this breaks the multicipher possibility.

viewtopic.php?p=599072#p599072

And after a closer look, I prefer my memory pointer solution.
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: hmac_sha1

Post by Seymour Clufley »

Thank you very much, Infratec. This has been a great help for me. You could rename the procedure to simply "HMAC", since it now supports the other ciphers as well.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

:mrgreen:

I already renamed it to StringHMAC()
And I just optimized the Hex string to bin conversion.
infratec
Always Here
Always Here
Posts: 7576
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: hmac_sha1

Post by infratec »

Added the possibility for different bit length in SHA2 and 3
Post Reply