HMAC function implementation

Share your advanced PureBasic knowledge/code with the community.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

HMAC function implementation

Post by Lunasole »

Hi, I just finished my HMAC thing for PB.
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

"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
timwil1963
New User
New User
Posts: 7
Joined: Sat Dec 28, 2019 4:26 pm

Re: HMAC function implementation

Post by timwil1963 »

Works perfectly

Thank you very much
Rinzwind
Enthusiast
Enthusiast
Posts: 636
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: HMAC function implementation

Post by Rinzwind »

You might want to use #PB_UTF8 as encoding to make it compatible with other generator results when using unicode strings.
https://www.freeformatter.com/hmac-gene ... #ad-output
http://beautifytools.com/hmac-generator.php

Just replace your #PB_ASCII with #PB_UTF8 on all spots. Results for ascii values will stay the same since utf-8 is backward compatible.


'random' test text: Begrüßenภาษาไทย!
SHA2: 7596eaf61caf36289d5b1c9c8780dc657767e867a00d079bd9da518bb254a6c3

Also another example of why PB should include hash functions that return raw byte data.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: HMAC function implementation

Post by Kwai chang caine »

Works fine here on W7 X86 / v5.70 LTS x86, thanks for sharing 8)
* HMAC of empty data and empty key *
MD5: 74e6f7298a9c2d168935f58c001bad88
SHA1: fbdb1d1b18aa6c08324b7d64b71fb76370690e1d
SHA2: b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad

* HMAC of control data to check is it OK *
MD5: 80070713463e7749b90c2dc24911e275
SHA1: de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
SHA2: f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
* fin *
ImageThe happiness is a road...
Not a destination
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: HMAC function implementation

Post by infratec »

There is a bug when a key is longer then the blocksize.
The fix is:

Code: Select all

If StringByteLength(Key$, #PB_Ascii) > #HMAC_BLOCKSIZE
    Key$ = StringFingerprint(Key$, PB_Cipher)
    For Tmp = 0 To 19
      key_bdata(Tmp) = Val("$" + Mid(Key$, Tmp * 2 + 1, 2))
    Next Tmp
  Else
    PokeS(@key_bdata(0), Key$, -1, #PB_Ascii | #PB_String_NoZero)
  EndIf
But this breaks the multiple cipher possibility, cause of the cipehre result length.

An other solution can be found here:
viewtopic.php?p=599038#p599038
Post Reply