HMAC - Cryptography API: Next Generation (CNG)
Algorithm creator(s)
Mihir Bellare, Ran Canetti, Hugo Krawczyk
PB author(s)
David Roberts
Description
Keyed-Hashing for Message Authentication. Hash-based Message Authentication Code (HMAC) is a message authentication code that uses a cryptographic key in conjunction with a hash function.
Note
Demonstrates the usage of MS' CNG (Crypt32.dll) to create a HMAC. Required PB #Include: WinCrypt.inc
Source
n/a
See also
Source Code
Download source code file BCryptHMACII.bas (Right-click -> "Save as ...")
#Compile Exe
#Dim All
#Register None
#Include "WIN32API.INC"
' A comprehensive Hash Function using Microsoft's 'Cryptography API: Next Generation'
' Requires OS >= Windows Vista and PB compilers 6 & 10
' For earlier compilers a zip of a dll is available (11KB) at http://www.deltarho.org.uk/Downloads/HashCNG.zip
' Use the following
' Declare Function Hash Lib "HashCNG.dll" Alias "Hash"( ByVal HashAlg As WString, ByVal sText As String, _
' Byval Index As long, Byval Mode As Long, ByVal Final As Long, Opt pbSecret As String ) As String
' Reccomendation
' Although the following function will accept an ASCII HMAC key it is not recommended.
' The strength of a cryptographic hash algorithm is half of it's bit size. A binary HMAC key of
' length equal to that is reccomended and since two hex characters are required per byte then a
' HMAC key in hex form needs to have the same number of hex characters as the hash algorithm has
' bytes. For example, SHA256 has 32 bytes (256/8) with a strength of 128 bits. 128 bits is 16
' bytes so we need 32 hex characters for the HMAC key. MD5 is a 16 byte hash so need a HMAC key
' of 16 characters; and so on.
'Parameters
' HashAlg [in] Hash algorithm as wide string
' Available values: "MD2"$$, "MD4"$$, "MD5"$$, "SHA1"$$, "SHA256"$$, "SHA384"$$ or "SHA512"$$
' "MD2"$$, "MD4"$$, "MD5"$$ are no longer recommended although "MD5"$$ may be used in a HMAC
' An empty string may be used for, if applicable, second or subsequent passes of the function.
' sText [in] Text string to be hashed which may or may not be empty.
' Index [in] Up to 8 (1 to 8) hash operations may exist at the same time.
' A used Index is freed when Final is true.
' Mode [in] If false the return value will be binary else hexadecimal; read only when Final is true.
' Final [in] If false then another pass of the function is required else the hash is finalized.
' If, for some reason, Final was not true on the last pass of a multi-pass use then the function
' should be employed again with sText as an empty string.
' The function returns an empty string until Final is true.
' pbSecret [in, optional]
' If chosen, pbSecret string is used as a HMAC key. If pbSecret has an even number of hexadecimal
' characters then it will be converted to a binary string unless it is concatenated with $Nul.
' This parameter may be ignored for, if applicable, second or subsequent passes of the function.
' ******************
Function Hash( ByVal HashAlg As WString, ByVal sText As String, ByVal Index As Long, _
ByVal Mode As Long, ByVal Final As Long, Opt pbSecret As String ) As String
Dim phHash(1 To 8) As Static Dword
Dim hAlg(1 To 8) As Static Dword
Local sBinary, sHex As String
Local nLength As Long
If IsFalse phHash(Index) Then ' Will be the case on, and perhaps only, first pass
If IsMissing( pbSecret ) Then ' not HMAC
BCryptOpenAlgorithmProvider hAlg(Index), ByVal StrPtr( HashAlg ), $$Nul, ByVal 0 ' We want hAlg(Index)
BCryptCreateHash hAlg(Index), phHash(Index), ByVal %Null, 0, 0, 0, 0 ' We want phHash(Index)
Else ' is HMAC
BCryptOpenAlgorithmProvider hAlg(Index), ByVal StrPtr( HashAlg ), $$Nul, _
%BCRYPT_ALG_HANDLE_HMAC_FLAG ' We want hAlg(Index)
If Right$( pbSecret, 1 ) <> $Nul Then ' ASCII not forced
' Are we going to use pbSecret as ASCII or Binary?
If ( Len( pbsecret ) Mod 2 = 0 ) And IsTrue IsHex( pbSecret ) Then
nLength = Len(pbSecret)\2
sBinary = String$( nLength, 0 )
CryptStringToBinary ByVal StrPtr(pbSecret), Len(pbSecret) ,%CRYPT_STRING_HEXRAW, _
ByVal StrPtr(sBinary), nLength, 0, 0
pbSecret = sBinary
End If
Else
pbSecret = Left$( pbSecret, Len(pbSecret) - 1)
End If
BCryptCreateHash hAlg(Index), phHash(Index), ByVal %Null, ByVal 0, StrPtr( pbSecret ), _
Len( pbSecret ), ByVal 0 ' We want phHash(Index)
End If
End If
BCryptHashData phHash(Index), ByVal StrPtr( sText), Len( sText ), ByVal 0
If IsTrue Final Then ' Finalize hash
Local lHashLength, lResult As Long
Local sBinHash As String
BCryptGetProperty hAlg(Index), $$BCRYPT_HASH_LENGTH, VarPtr( lHashLength ), Len( lHashLength ), _
lResult, ByVal 0 ' We want lHashLength
sBinHash = String$( lHashLength, 0 )
BCryptFinishHash phHash(Index), StrPtr( sBinHash ), lHashLength, ByVal 0
BCryptDestroyHash phHash(Index)
BCryptCloseAlgorithmProvider hAlg(Index), ByVal 0
Reset phHash(Index) ' Ensures a new hash is created on another pass of Hash() with this Index
If IsFalse Mode Then
Function = sBinHash
Else
nLength = Len(sBinHash)*2 + 1 ' + 1 to accomodate a null terminator
sHex = String$( nLength, 0 )
CryptBinaryToString ByVal StrPtr(sBinHash), Len(sBinHash), %CRYPT_STRING_HEXRAW + _
%CRYPT_STRING_NOCRLF, ByVal StrPtr(sHex), nLength
' nLength 'out' is Len(sBinHash) * 2
Function = UCase$( Left$( sHex, nLength ) )
End If
End If
End Function
' ******************
FUNCTION IsHex(s as string) AS LONG
register i AS LONG
local sPtr as byte pointer
sPtr = strptr(s)
FOR i = 0 TO LEN(s) - 1
SELECT CASE as long @sPtr[i]
CASE 48 TO 57, 65 TO 70, 97 TO 102
CASE ELSE
EXIT FUNCTION
END SELECT
NEXT i
FUNCTION = %True
END FUNCTION
' ******************
' EXAMPLE USAGE
Function PBMain() As Long
Local sHash, msg, sText As String
Local Hash1, Hash2 As String
' ****** Hash ******
' Single input
sText = "Compile without compromise"
sHash = Hash( "SHA256"$$, sText, 1, %True, %True ) ' Hash() passed once
msg = sHash + $CrLf
' Packets
sText = "Compile"
Hash "SHA256"$$, sText, 1, %True, %False ' Further pass required
sText = " without"
Hash "", sText, 1, %True, %False ' Further pass required
sText = " compromise"
sHash = Hash( "", sText, 1, %True, %True ) ' Final pass
msg += sHash
MsgBox msg,,"Single input vs packets"
' ==> C52395EB6DCCC0BF1D45A41B489691D572406461FD5FA67F5928A2E9C2F2057A
' Confirmed by SlavaSoft's HashCalc: http://www.slavasoft.com/hashcalc/
' ******************
' ****** HMAC ******
' Single input
sText = "Compile without compromise"
sHash = Hash( "MD5"$$, sText, 1, %True, %True, "2A9D5DCB9DE7C67E" ) ' Hash() passed once
' 16 character HMAC key used
msg = sHash + $CrLf
' Packets
sText = "Compile"
Hash "MD5"$$, sText, 1, %True, %False, "2A9D5DCB9DE7C67E" ' Further pass required
sText = " without"
Hash "", sText, 1, %True, %False ' Further pass required
stext = " compromise"
sHash = Hash( "", sText, 1, %True, %True ) ' Final pass
msg += sHash
MsgBox msg,,"Single input vs packets (HMAC)"
' ==> 3715B85D6E6CE8C2BAB473FB4225A40D
' Confirmed by SlavaSoft's HashCalc
' ******************
' Concurrent Hashing
' Two hashes using the same input data to give the same output after interleaving
sText = "HousesOf"
Hash "SHA1"$$, sText, 1, %True, %False ' Further pass required for Index = 1
Hash "SHA1"$$, sText, 2, %True, %False ' Further pass required for Index = 2
sText = "Parliament"
Hash1 = Hash( "", sText, 1, %True, %True ) ' Final pass
Hash2 = Hash( "", sText, 2, %True, %True ) ' Final pass
MsgBox Hash1 + $CrLf + Hash2,, "Concurrent Hashing"
' ==> 679AD50A945A48DA10D31B515AB5AA08EC6ECC4E
' Confirmed by SlavaSoft's HashCalc
' ******************
' ****** File Hashing ******
Static TempStr As String * 10*1024*1024
Local i As Long
' Generate a 10MB file of random numbers
Dim rndArray(1 To 10*1024*1024) As Byte At VarPtr(TempStr)
Randomize 666
For i = 1 To 10*1024*1024
rndArray(i) = Rnd(0,255)
Next
Open "Test.dat" For Binary As #1
Put$ #1, Tempstr
Close #1
' Now HMAC it
Open "Test.dat" For Binary As #1
Do
Get$ #1, 256*1024, sText ' Using a 256KB buffer
Hash "SHA256"$$, sText, 1, %True, %False, "7474F71275E25561B0DB2CABFBF11EAC"
' 32 character HMAC key
Loop Until Eof(#1)
Close #1
' The hash has not been finalized so employ Hash() again with an empty string.
Reset sText
sHash = Hash( "", sText, 1, %True, %True )
' On my machine the above took 52ms
MsgBox sHash,, "File Hashing"
' ==> 6F6B3E46E01ADD058065AF15F6C245AFB0B92E79AD816E1EEE66EFB5C3521F09
' Confirmed by SlavaSoft's HashCalc and author's HashFile
Kill "Test.dat"
' ******************
' ****** Password stretching and verification ******
' Algorithm employed x0 = 0 : xi = Hash( xi-1 + sPassword + sPasswordSalt ) For i = 1 To some n.
' From Cryptography Engineering: ISBN 978-0-470-47424-2
' The idea here is to increase the entropy of a limited-entropy password. I take the view that
' we should not be considering using a limited-entropy password. However, I have no objection
' to strengthening an already strong password.
Local sDummy, x, sPassword, sPasswordsalt, sVerifyValue, sStretch As String
Local nLength As Long
sPassword = "TrafalgarSquare"
sPasswordSalt = Chr$(26) + Chr$(45) + Chr$(123) + Chr$(14)
' In practice, sPasswordSalt will be a much longer binary key, got from,
' perhaps, the header of an encrypted file; computed as random during encryption.
' Iterate 10,000 times
For i =1 To 9999
sDummy = x + sPassword + sPasswordSalt
x = Hash( "SHA256"$$, sDummy, 1, %False, %True )
Next
sDummy = String$(64,0) + x + sPassword + sPasswordSalt
sVerifyValue = Hash( "SHA256"$$, sDummy, 1, %False, %True )
' SVerifyValue would be compared to a 'sSavedVerifyValue got from, perhaps, the header
' of an encrypted file
' Now compute final iterate
sDummy = x + sPassword + sPasswordSalt
x = Hash( "SHA256"$$, sDummy, 1, %False, %True )
' On my machine the above took 240ms
' On a similar machine an attack would take 4 guesses per second
' In practice, the final value of x will be kept as binary
' Converted to hex here for display purposes.
nLength = Len(x)*2 + 1
sStretch = String$( nlength, 0 )
CryptBinaryToString ByVal StrPtr(x), Len(x), %CRYPT_STRING_HEXRAW + _
%CRYPT_STRING_NOCRLF, ByVal StrPtr(sStretch), nLength
MsgBox UCase$( Left$( sStretch, nLength ) ),, "Password stretching and verification"
' ==> C8E5D895493FA960BF1F1E239DBFC993001D02C3730FB5C4BCD5EDAEC70EE882
' ******************
End Function