Creating a HMAC using Microsoft's CryptoAPI is not straightforward. When I wrote SecureHash.sll I hid the complexity from the user requiring only a HMAC key as an additional parameter to hashing only. Creating a HMAC using 'Cryptography API: Next Generation.' (CNG) with Windows Vista and above is almost as easy.
Of all the hashing algorithms available I find myself using SHA256 the most so the following function, Hash(), is SHA256 hard-wired; but can be easily adapted for other hash algorithms.
The first pass of Hash() requires that the second parameter, phHash, is zero - this ensures that a hash object is created. During the first pass the first or only message, sText, is hashed. If Final is false then further messages are expected and they will be 'hashed into' the hash object. If Final is true then the last or only message is 'hashed into' the hash object and the hash is finished; the hash object is destroyed and phHash is reset in preparation of a new sequence of Hash(), if any.
Most of my hashing work remains in the binary domain so Hash() outputs binary; when Final is true.
In the following example the first part uses Hash() for hashing only and we process three messages and then process a concatenation of the three messages giving, of course, the same hash result.
Concatenation is not always possible - we may be reading a file via a buffered input - we cannot hash a second buffer (message) until the first buffer (message) is read, and so on.
The second part uses Hash() to create a HMAC and, again, we process three messages and then a concatenation.
The HMAC is 'forced' by using BCRYPT_ALG_HANDLE_HMAC_FLAG in the BCryptOpenAlgorithmProvider API and the secret HMAC key is introduced in BCryptCreateHash.
This is how it should have been done in CryptoAPI.
The concatenation results have been verified using other software.
I wish that I had gotten into CNG sooner. At first it looked more daunting than CryptoAPI but it turns out to be easier to use.
Of all the hashing algorithms available I find myself using SHA256 the most so the following function, Hash(), is SHA256 hard-wired; but can be easily adapted for other hash algorithms.
The first pass of Hash() requires that the second parameter, phHash, is zero - this ensures that a hash object is created. During the first pass the first or only message, sText, is hashed. If Final is false then further messages are expected and they will be 'hashed into' the hash object. If Final is true then the last or only message is 'hashed into' the hash object and the hash is finished; the hash object is destroyed and phHash is reset in preparation of a new sequence of Hash(), if any.
Most of my hashing work remains in the binary domain so Hash() outputs binary; when Final is true.
In the following example the first part uses Hash() for hashing only and we process three messages and then process a concatenation of the three messages giving, of course, the same hash result.
Concatenation is not always possible - we may be reading a file via a buffered input - we cannot hash a second buffer (message) until the first buffer (message) is read, and so on.
The second part uses Hash() to create a HMAC and, again, we process three messages and then a concatenation.
The HMAC is 'forced' by using BCRYPT_ALG_HANDLE_HMAC_FLAG in the BCryptOpenAlgorithmProvider API and the secret HMAC key is introduced in BCryptCreateHash.
This is how it should have been done in CryptoAPI.

The concatenation results have been verified using other software.
I wish that I had gotten into CNG sooner. At first it looked more daunting than CryptoAPI but it turns out to be easier to use.
Code:
#Compile Exe #Dim All #Register None #Include "WIN32API.INC" Function PBMain() As Long Local hHashAlg, hHMACAlg, phHash As Dword Local sText, sBinHash, msg As String ' ****** Hash ****** BCryptOpenAlgorithmProvider( hHashAlg, "SHA256"$$, $$Nul, 0) Hash( hHashAlg, phHash, "Compile", %False ) Hash( hHashAlg, phHash, " without", %False ) sBinHash = Hash( hHashAlg, phHash, " compromise", %True ) msg = Bin2Hex( sBinHash ) + $CrLf sBinHash = Hash( hHashAlg, phHash, "Compile without compromise", %True ) msg += Bin2Hex( sBinHash ) MsgBox msg BCryptCloseAlgorithmProvider( hHashAlg, 0 ) ' ****** ' ****** HMAC ****** BCryptOpenAlgorithmProvider( hHMACAlg, "SHA256"$$, $$Nul, %BCRYPT_ALG_HANDLE_HMAC_FLAG) Hash( hHMACAlg, phHash, "Compile", %False, "Secret" ) Hash( hHMACAlg, phHash, " without", %False ) sBinHash = Hash( hHMACAlg, phHash, " compromise", %True ) msg = Bin2Hex( sBinHash ) + $CrLf sBinHash = Hash( hHMACAlg, phHash, "Compile without compromise", %True, "Secret" ) msg += Bin2Hex( sBinHash ) MsgBox msg BCryptCloseAlgorithmProvider( hHMACAlg, 0 ) ' ****** End Function Function Hash( ByVal hAlg As Dword, phHash As Dword, sText As String, ByVal Final As Long, Opt pbSecret As String ) As String ' Input ' hAlg As given by BCryptOpenAlgorithmProvider ' sText Is message To be hashed ' Final Is false If further messages are To be 'hashed In' Else true. ' pbSecret Is secret HMAC key ' Input/Output ' phHash Is zero On first pass Of Hash() And As given by BCryptCreateHash On second And subsequent passes, if any Local sBinHash As String * 32 ' Hard wired At 32 For SHA256 If IsFalse phHash Then If IsMissing( pbSecret ) Then BCryptCreateHash( hAlg, phHash, %Null, 0, 0, 0, 0 ) Else BCryptCreateHash( hAlg, phHash, ByVal %Null, 0, StrPtr( pbSecret ), Len( pbSecret ), 0 ) End If End If BCryptHashData( phHash, StrPtr( sText ), Len( sText ), 0 ) If IsTrue Final Then BCryptFinishHash( phHash, VarPtr( sBinHash ), 32, 0 ) BCryptDestroyHash( phHash ) Reset phHash ' Ensures that Next time Hash() Is used a hash Object Is created Function = sBinHash End If End Function Function Bin2Hex( ByVal strSource As String ) As String ' Input Binary String ' Output Hex String Local i As Long Local ptrSource As Byte Ptr Local strDestination As String Local ptrDestination As String Ptr ptrSource = StrPtr( strSource ) strDestination = Space$( 2 * Len( strSource ) ) ptrDestination = StrPtr(strDestination) For i = 0 To Len( strSource ) - 1 Poke$ ptrDestination, Hex$( @ptrSource[i], 2 ) ptrDestination += 2 Next Function = strDestination End Function
Comment