It depends on PB hashing functions, so theoretically any of PB "ciphers" should work well, but currently tested only with MD5, SHA-1 and SHA-2 (SHA256).
Also would be nice if someone can compare its results with results of that php hash_hmac() function (as this hashing usually used with those kid-or-monkey-oriented web-apis :3).
Any feedback welcome
Code: Select all
EnableExplicit
; HMAC function implementation
; 2016 (c) Luna Sole
; 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$)
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_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
; some tests
Debug "* HMAC of empty data and empty key *"
Debug "MD5: " + StringHMAC(#PB_Cipher_MD5, "", "") ; MD5 = 0x74e6f7298a9c2d168935f58c001bad88
Debug "SHA1: " + StringHMAC(#PB_Cipher_SHA1, "", "") ; SHA1 = 0xfbdb1d1b18aa6c08324b7d64b71fb76370690e1d
Debug "SHA2: " + StringHMAC(#PB_Cipher_SHA2, "", "") ; SHA256 = 0xb613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad
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 = 0x80070713463e7749b90c2dc24911e275
Debug "SHA1: " + StringHMAC(#PB_Cipher_SHA1, "The quick brown fox jumps over the lazy dog", "key") ; SHA1 = 0xde7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
Debug "SHA2: " + StringHMAC(#PB_Cipher_SHA2, "The quick brown fox jumps over the lazy dog", "key") ; SHA256 = 0xf7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
Debug "* fin *"
; Control data and resulting HMAC-hashes for it taken from wiki page:
; https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Examples