2024-04-29

Navigation

Skip Navigation Links

Hash algorithms

Asymmetric Algorithms

Symmetric Cipher Algorithms

Encoding Algorithms

Compression Algorithms

Pseudo Random Number Algorithms

Steganography

Library Wrappers

String Comparison

Others

Syntax highlighting by Prism
PBCrypto.com Mirror

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

Mirror provided by Knuth Konrad