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

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
https://www.purebasic.fr/english/viewtopic.php?t=81309
MD4.pbi:
https://www.purebasic.fr/english/viewtopic.php?t=81310