Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Dynamic HMAC

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Dynamic HMAC

    Preamble

    HMAC was proposed in [1] and there is a particularly good interpretation at [2]

    Effectively, we have

    HMAC(K,m) = H( a || H( b || m ) ) ...... (i)

    where K is the shared/secret key, m is the message, H is the hash algorithm and || to be read as concatenate.

    a = K0 XOR opad and b = K0 XOR ipad where ipad = &H36 and opad = &H5C both repeated B, the block size, times.

    Len(K0) = B and

    K0 = K if Len(K) = B
    = K || 0...0 if K < B
    = H(K) || 0...0 if K > B

    The block size, B, is 64 bytes for both MD5 and SHA1, and many others for that matter.

    A quotation from Microsoft: "A hash can be created in a piece-by-piece way."

    H( b || m ) may be computed with b || m as a single stream or we can compute H( b ) and later 'hash in' m.

    H( b || m ) could be written H( b ).m where the .m means 'hash in' m.

    (i) can now be written HMAC(K,m) = H( a ).H( b ).m ....... (ii)

    (i) is the format used when m is the message in text form with b || m treated as a single stream. If we call the resulting hash hBin we then treat a || hBin as a single stream.

    (ii) is the format used when m is the message in file form. Here we hash b then hash in m. With the resulting hash as hBin we then, as above, treat a || hBin as a single stream.

    D-HMAC, Dynamic HMAC, proposed in [3], calculates an ipad and opad depending upon both the message and a receiver key and the core of the idea is the use of

    H(m) XOR R where R is a receiver key ............... (iii)

    So, if we send the same message to different receivers then the D-HMAC values will be different. If we send different messages to the same receiver then the D-HMAC values will be different. In the latter case, the HMAC values would be different anyway but they would be determined with static ipad and opad.

    I should add that I have got the OK to use the algorithm by Mohannad Najjar one of the authors of D-HMAC.

    The algorithm examines (iii) at the bit level. The leftmost bit determines which row of a substitution box to use. The next two bits determine which column to use. The second bit is then used to give the row and the two bits following give the column and so on. Inserted: Didn't finish the story. The process so far gets us an entry for ipad. Whichever row is chosen the other row is used for getting the opad entry. End of insertion. [3] does not indicate what to do when we reach the 127th bit, requiring a further two bits for the row, but the natural thing to do is to simply rotate right and pick up the first bit again; which has only been used once so far. At the 128th bit we rotate twice and pick up the first two bits. This way all bits get used three times, firstly to give the row and then the second or third bit of three in determining the column. The substitution box gives four bits so for MD5 we get 128*4 bits output ie 64 bytes, the same as the block size. With SHA1, being 160 bits, we would exceed the block size. No remedy for this is given in [3]. The implemented code simply restricts the input to 128 bits and if the receiver key exceeds 128 bits then this is hashed down to 128 bits by hashing it with MD5.

    Since m plays a part in both (i) and (iii) then we can expect the computational time of D-HMAC to be of the order of twice that of HMAC when the message is significantly larger than 64 bytes. The additional work of D-HMAC over HMAC, excepting (iii), as with the additional work of HMAC over H(ash) is not dependent upon the message size.

    With two receivers and one sender it may be worthwhile to examine how much difference there is in the D-HMAC values.

    Given two strings, A and B, of the same length we can measure their difference at the bit level by counting the set bits of A Xor B. With A Xor A there are no set bits, not surprising since there is no difference between A and A. With A Xor Not A then we have nothing but set bits because there is nothing in common. If A and B are random then for an infinite of number of pairs we will find an average of 25% set collisions, 25% reset collisions, 25% 0 1 combinations and 25% 1 0 combinations. In other words 50% different and, obviously, 50% in common. Dividing the set count by the string bit length will give a percentage difference.

    The following code considers, DHMAC, HMAC and finally H, the standard hash function, implementing the Windows inbuilt MD5 and SHA1. For D-HMAC a sender key plus two receiver keys, all randomly generated, are considered. For each pair of receiver keys a percentage difference is calculated. The percentage difference should fall, on average, between 40% and 60% about 97% of the time for 128 bit strings (MD5) and about 99% of the time for 160 bit string (SHA1). Leaving the machine on overnight for a very thorough test has not been done yet but with a limited test there does not seem to be any signs of a difference different to what we would expect of random strings. Consideration was also given to one receiver and two senders where the sender keys differed only by one bit in the lowest significant byte. Again no unusual differences were found in the resulting D-HMAC pairs.

    As expected D-HMAC takes about twice as long as HMAC and H, the latter two being in the same ball park. This was for files ranging from 1MB to 100MB. Less than 1MB on my system sees timings of around 44ms and less, including reading from the hard drive, and the pattern gets distorted. For small messages the code would require optimizing but squeezing every ounce on speed grounds was not a criteria here - getting working code for the principle was the order of the day.

    At one point I considered a revised HMAC viz H( a ).H( m ).b ...... (iv)
    figuring that H( m ) used in (iii) could be used again in (iv) and thus half the computational time.

    However, if we have H( m1 ) = H( m2 ) with m1 <> m2 then we have a collision.

    So, H( m1 ).x = H( m2 ).x and H( y ).H( m1 ).x = H( y ).H( m2 ).x

    The HMAC will then also collide rendering (iv) as a non-runner as it would have a collision resistance no better than a standard hash.

    It is worth noting that H( x ).m1 will probably <> H( x ).m2, the probability being 2^2n to 1 against if the probability of the first collision is 2^n to 1 against.

    This is why HMAC uses H( b ).m and not H( m ).b

    When Mohannad Najjar wrote me he said "We are doing some changes on the algorithm to make it more powerful; hopefully we will publish the improved algorithm soon."

    I would like to see a more heavy duty substitution box and will start a thread in the Programming Forum inviting folk to chip in with their experience - it is not an area that I am well acquainted with. The substitution box used is at the bottom of the GetIpadOpad function.


    References

    [1] M Bellare, R Canetti and H Krawczyk
    Message Authentication using Hash Functions - The HMAC Construction

    [2] FIPS PUB 198-1
    The Keyed-Hash Message Authentication Code (HMAC)

    [3] M Najjar and F Najjar
    d-HMAC Dynamic HMAC function
    Last edited by David Roberts; 25 Mar 2008, 01:24 PM. Reason: Inserted text

  • #2
    Code. D-HMAC.inc in next post

    Code:
    ' 8.04
    #COMPILE EXE
    #DIM ALL
    #REGISTER NONE
    #TOOLS OFF
    %NOMMIDS = 1
    %NOGDI = 1
    
    #INCLUDE "D_HMAC.inc"
    
    '~~~~~~~~~~~~~
    
    $App = "D_HMAC"
    $DblQuote = CHR$(34)
    %Bin = 1
    %Hex = 0
    %BufferSize = 65536
    %Alg = %CALG_SHA1
    '%Alg = %CALG_MD5
    %File = %True ' If False then supply text
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    MACRO KXorPad( pad, filter )
    MACROTEMP LoopPoint
    
      !mov eax, pad
      !mov ebx, filter
      !mov ecx, 16
      LoopPoint :
      !Xor [eax], ebx
      !Add eax, 4
      !dec ecx
      !jnz LoopPoint
    
    END MACRO
    
    MACRO BuildByte( row0, col0, row1, col1 )
    ' Put row0/col0 into upper nybble & row1/col1 into lower nybble of result
    
      ! mov esi, row0
      ! mov ebx, col0
      ! mov al, Byte Ptr [esi + ebx]
      ! shl al, 4
      ! mov esi, row1
      ! mov ebx, col1
      ! mov bl, Byte Ptr [esi + ebx]
      ! Add al, bl
      ! mov result, al ' result available to BASIC
    
    END MACRO
    
    MACRO SysErrMsg(API, Number) = MessageBox 0, SystemErrorMessage( WinErr, API, FUNCNAME$, Number ), $App, %MB_ICONERROR + %MB_TOPMOST
    
    GLOBAL qFreq, qOverhead, qStart, qStop AS QUAD
    GLOBAL f AS STRING
    
    MACRO InitializeTimer
      f = "#####.#"
      QueryPerformanceFrequency qFreq
      QueryPerformanceCounter qStart
      QueryPerformanceCounter qStart
      QueryPerformanceCounter qStop
      qOverhead = qStop - qStart
    END MACRO
    
    MACRO StartTimer = QueryPerformanceCounter qStart
    MACRO StopTimer = QueryPerformanceCounter qStop
    
    MACRO TimeTaken = USING$(f,(qStop - qStart - qOverhead)*1000/qFreq) + "ms"
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    DECLARE FUNCTION crGetDefaultRSAHandle AS LONG
    DECLARE FUNCTION CreateHash( LONG, LONG, LONG ) AS LONG
    DECLARE FUNCTION GetHash( STRING , LONG, BYVAL LONG ) AS LONG
    DECLARE FUNCTION TextToHash( STRING, LONG ) AS LONG
    DECLARE FUNCTION FileToHash( sFile AS STRING, hHash AS LONG ) AS LONG
    DECLARE FUNCTION D_HMAC( STRING, STRING, STRING, LONG, STRING ) AS LONG
    DECLARE FUNCTION HMAC( STRING, STRING, LONG, STRING ) AS LONG
    DECLARE FUNCTION H( STRING, LONG, STRING ) AS LONG
    DECLARE FUNCTION AXorB( STRING, STRING ) AS LONG
    DECLARE FUNCTION GetIpadOpad( STRING, STRING, STRING ) AS LONG
    DECLARE FUNCTION SystemErrorMessage( BYVAL LONG, STRING, STRING, BYVAL LONG ) AS STRING
    DECLARE FUNCTION GetASessionKey( LONG ) AS STRING
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION PBMAIN
    
      DIM stext AS STRING
      LOCAL MsgBoxText, HashHex, HashHex0, alg, Percent AS STRING
      LOCAL SenderKey, ReceiverKey1, ReceiverKey2, sDHMAC0, sDHMAC1 AS STRING
      LOCAL psDHMAC0 AS BYTE PTR
      LOCAL NoOfBytes, i, nval, dist, count AS LONG
    
      InitializeTimer
    
      SenderKey  = GetASessionKey( %Alg )
      IF SenderKey = "" THEN MSGBOX "GetASessionKey failed" : EXIT FUNCTION
      ReceiverKey1  = GetASessionKey( %Alg )
      IF ReceiverKey1 = "" THEN MSGBOX "GetASessionKey failed" : EXIT FUNCTION
    
      #IF %File = %True
        sText = "C:\WINDOWS\system32\shell32.dll" '                             <--------- What do you want to do today?
        MsgBoxText = sText + $CRLF + $CRLF
      #ELSE
        sText = STRING$(1000000, "a") '                                         <--------- To be or not to be, ...
      #ENDIF
    
      ' D-HMAC ------------------------------------------------
    
      IF D_HMAC(sText, SenderKey, ReceiverKey1, %ALG, HashHex) = %False THEN
        EXIT FUNCTION
      ELSE
        HashHex0 = HashHex
      END IF
    
      ' Convert HashHex to a binary string
      NoOfBytes = LEN( HashHex )/2
      sDHMAC0 = ""
      FOR i = 0 TO NoOfBytes - 1
        sDHMAC0 = sDHMAC0 + CHR$( VAL( "&H" + MID$( HashHex, 2*i+1, 2 ) ) )
      NEXT
    
      ReceiverKey2 = GetASessionKey( %Alg )
      IF ReceiverKey2 = "" THEN MSGBOX "GetASessionKey failed" : EXIT FUNCTION
    
      StartTimer
      IF D_HMAC(sText, SenderKey, ReceiverKey2, %ALG, HashHex) = %True THEN
        StopTimer
        MsgBoxText = MsgBoxText + "D-HMAC" + TimeTaken + $CRLF + $CRLF + "Receiver 1:  " + HashHex0 + $CRLF + "Receiver 2:  " + HashHex + $CRLF + $CRLF
      END IF
    
      ' Convert HashHex to a binary string
      sDHMAC1 = ""
      FOR i = 0 TO NoOfBytes - 1
        sDHMAC1 = sDHMAC1 + CHR$( VAL( "&H" + MID$( HashHex, 2*i+1, 2 ) ) )
      NEXT
    
      AXorB( sDHMAC0, sDHMAC1 ) ' No of set bits in sDHMAC0 is strength of difference
    
      ' Calculate no of set bits
      psDHMAC0 = STRPTR(sDHMAC0)
      count = 0
      FOR i = 0 TO NoOfBytes - 1
        nval = @psDHMAC0
        dist = 0
        WHILE nval
          INCR dist
          nval = nval AND (nval - 1)
        WEND
        INCR psDHMAC0
        count = count + dist
      NEXT
    
      Percent = USING$("##.#",count*100/(NoOfBytes*8))
    
      MsgBoxText = MsgBoxText + "    Bit Difference = " + Percent + "%"+ $CRLF + $CRLF
    
      ' -------------------------------------------------------
    
      ' HMAC --------------------------------------------------
    
      StartTimer
      IF HMAC(sText, SenderKey, %ALG, HashHex) = %True THEN
        StopTimer
        MsgBoxText = MsgBoxText + "HMAC" + TimeTaken + $CRLF + $CRLF + HashHex + $CRLF + $CRLF
      END IF
    
      ' -------------------------------------------------------
    
      ' H -----------------------------------------------------
    
      StartTimer
      IF H(sText, %ALG, HashHex) = %True THEN
        StopTimer
        MsgBoxText = MsgBoxText + "H" + TimeTaken + $CRLF + $CRLF + HashHex
      END IF
    
      ' -------------------------------------------------------
    
      MSGBOX MsgBoxText, %MB_TOPMOST, $App
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION crGetDefaultRSAHandle AS LONG
    
      LOCAL hProv, WinErr AS LONG
    
      IF CryptAcquireContext( hProv, BYVAL %Null, BYVAL %Null, %PROV_RSA_FULL, 0 ) = %FALSE THEN
        IF CryptAcquireContext( hProv, BYVAL %Null, $MS_DEF_PROV, %PROV_RSA_FULL, _
        %CRYPT_VERIFYCONTEXT ) = %FALSE THEN
          WinErr = GetLastError : SysErrMsg( "CryptAcquireContext", 10 )
          FUNCTION = %FALSE
        ELSE
          FUNCTION = hProv
        END IF
      ELSE
        FUNCTION = hProv
      END IF
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION CreateHash( hProv AS LONG, Which AS LONG, hHash AS LONG ) AS LONG
    
      LOCAL WinErr AS LONG
    
      IF CryptCreateHash( hProv, Which, 0, 0, hHash ) = %FALSE THEN
        WinErr = GetLastError : SysErrMsg( "CryptCreateHash" , 10 )
      ELSE
        FUNCTION = %TRUE
      END IF
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION GetHash( hText AS STRING , hHash AS LONG, BYVAL Flag AS LONG ) AS LONG
      ' If Flag = 1 then hText output is binary else hex
    
      LOCAL i, hSize, WinErr AS LONG
      DIM hByte( )   AS BYTE
      Dim hByteHex() As String * 2
    
      IF CryptGetHashParam( hHash, %HP_HASHSIZE, hSize, 4, 0 ) = %FALSE THEN
        WinErr = GetLastError : SysErrMsg( "CryptGetHashParam", 10 )
        EXIT FUNCTION
      END IF
      REDIM hByte( hSize - 1 ) AS BYTE
      IF CryptGetHashParam( hHash, %HP_HASHVAL, hByte( 0 ), hSize, 0 ) = %FALSE THEN
        WinErr = GetLastError : SysErrMsg( "CryptGetHashParam", 20 )
        EXIT FUNCTION
      END IF
      hText = ""
      IF Flag = %Bin THEN
        ' Remove commented code: 27 March 08
        'FOR i = 0 TO hSize - 1
        '  hText = hText + CHR$( hByte( i ) )
        'NEXT
        ' and replace with
        hText = Peek$( VarPtr( hByte(0) ), hSize ) ' Credit to Kev Peel re another thread
      ELSE
        ' Remove commented code: 29 March 08
        'FOR i = 0 TO hSize - 1
        '  hText = hText + HEX$( hByte( i ),2 )
        'NEXT
        ' and replace with
        hText = Space$( 2*hSize )
        ReDim hByteHex( hSize - 1 ) As String * 2 At StrPtr( hText ) ' Credit to John Gleason
        For i = 0 To hSize - 1
          hByteHex(i) = Hex$( hByte( i ),2 )
        Next
      END IF
    
      ' hHash is rendered useless after CryptGetHashParam so we may as well destroy it here
      CryptDestroyHash hHash
    
      FUNCTION = %TRUE
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION TextToHash( sText AS STRING, hHash AS LONG ) AS LONG
    
      LOCAL WinErr, Chunk, NextChunk, Balance, Completed, dummy AS LONG
      DIM ChunkText AS STRING * %BufferSize
    
      Chunk = %BufferSize ' Any particular reason? Nope!
      dummy = LEN( sText )
      Balance = dummy MOD Chunk
      Completed = %FALSE
      NextChunk = 1
      DO
        ChunkText = MID$( sText, NextChunk, Chunk )
        IF NextChunk + Chunk >= dummy THEN
          IF Balance = 0 THEN EXIT LOOP
          Chunk = Balance
          Completed = %TRUE
        END IF
        IF CryptHashData( hHash, ChunkText, Chunk, 0 ) = %FALSE THEN
          WinErr = GetLastError : SysErrMsg( "CryptHasData", 10 )
          EXIT FUNCTION
        END IF
        NextChunk = NextChunk + Chunk
      LOOP UNTIL completed
    
      FUNCTION = %TRUE
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION FileToHash( sFile AS STRING, hHash AS LONG ) AS LONG
    
    LOCAL hFile AS LONG
    LOCAL zFile AS ASCIIZ * 256
    LOCAL nbLoaded AS DWORD
    DIM Buffer AS STRING * %BufferSize
    LOCAL WinErr AS LONG
    LOCAL ReadBuffer AS LONG POINTER
    
      ReadBuffer = VARPTR(Buffer)
      zFile = sFile
      hFile = CreateFile(zFile, %GENERIC_READ, 0, BYVAL 0, %OPEN_EXISTING, %FILE_FLAG_SEQUENTIAL_SCAN OR %FILE_FLAG_NO_BUFFERING, BYVAL 0)
    
      DO
        IF ReadFile(hFile, BYVAL ReadBuffer, %BufferSize, nbLoaded, BYVAL 0) = 0 THEN
          WinErr = GetLastError : SysErrMsg( "ReadFile", 10 )
          CloseHandle(hFile)
          EXIT FUNCTION
        END IF
        IF nbLoaded = 0 THEN EXIT LOOP
        IF CRYPTHASHDATA( hHash, BYVAL ReadBuffer, nbLoaded, 0 ) = %False THEN
          WinErr = GetLastError : SysErrMsg( "CryptHashData", 20 )
          CloseHandle(hFile)
          EXIT FUNCTION
        END IF
      LOOP UNTIL nbLoaded < %BufferSize
    
      CloseHandle(hFile)
    
      FUNCTION = %True
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION D_HMAC( sText AS STRING, SenderKey AS STRING, ReceiverKey AS STRING, WhichHash AS LONG, HashHex AS STRING ) AS LONG
    
      ' Input: sText is the message - text string, binary string, binary file or whatever
      ' Output: HashHex is the D-HMAC value as a hex string
    
      #REGISTER NONE
      LOCAL hProv, hHash, dummy AS LONG
      DIM ipad       AS STRING
      DIM opad       AS STRING
      DIM hBin AS STRING
    
      hProv = crGetDefaultRSAHandle ' Our first job
      'IF hProv = 0 THEN GOTO TidyUp [COLOR="Red"]REMOVE Ported from another app where other tidying was done[/COLOR]
      If hProv = 0 Then Exit Function
    
      dummy = LEN(SenderKey)
      IF dummy <> 64 THEN
        IF dummy < 64 THEN
          SenderKey = SenderKey + STRING$(64 - dummy, 0) ' Pad with zeros
        ELSE
          IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
          IF TextToHash( SenderKey, hHash ) = %False THEN GOTO TidyUp  ' Hash SenderKey
          IF GetHash( SenderKey, hHash, %Bin) = %False THEN GOTO TidyUp' and get result
          SenderKey = SenderKey + STRING$(64 - LEN( SenderKey ), 0)
        END IF
      END IF
    
      dummy = LEN(ReceiverKey)
      IF dummy*8 > 128 THEN ' use MD5 to hash it down to 128
        IF CreateHash( hProv, %CALG_MD5, hHash ) = %FALSE THEN GOTO TidyUp
        IF TextToHash( ReceiverKey, hHash ) = %False THEN GOTO TidyUp  ' Hash ReceiverKey
        IF GetHash( ReceiverKey, hHash, %Bin) = %False THEN GOTO TidyUp' and get result. hHash is destroyed.
      END IF
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      ' Hash sText
    
      #IF %File = %True
        IF FileToHash( sText, hHash) = %False THEN GOTO TidyUp ' Hash the mesaage
      #ELSE
        IF TextToHash( sText, hHash ) = %False THEN GOTO TidyUp
      #ENDIF
    
      ' Get Hash of sText into hBin - binary string. hHash is destroyed.
      IF GetHash( hBin, hHash, %Bin ) = %False THEN GOTO TidyUp    ' h(m)
      IF AXorB( hBin, ReceiverKey ) = %False THEN GOTO TidyUp      ' h(m) becomes h(m) Xor ReceiverKey
      GetIpadOpad hBin, ipad, opad
      IF AXorB( ipad, SenderKey ) = %False THEN GOTO TidyUp        ' ipad becomes ipad Xor SenderKey
      IF AXorB( opad, SenderKey ) = %false THEN GOTO TidyUp        ' opad becomes opad Xor SenderKey
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      ' Step 1, Hash ipad + sText
      #IF %File = %True
        IF TextToHash( ipad, hHash ) = %False THEN GOTO TidyUp
        IF FileToHash( sText, hHash) = %False THEN GOTO TidyUp
      #ELSE
        IF TextToHash( ipad + sText, hHash ) = %False THEN GOTO TidyUp
      #ENDIF
    
      ' Step 2, hBin is the result of hashing ipad + sText  - binary string. hHash is destroyed.
      IF GetHash( hBin, hHash, %Bin ) = %False THEN GOTO TidyUp
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      ' Step 3, Hash opad + hBin
      IF TextToHash( opad + hBin, hHash ) = %False THEN GOTO TidyUp
    
      ' Step 4, HashHex is the result of TextToHash( opad + hBin ) - hex string. hHash is destroyed.
      IF GetHash( HashHex, Hhash, %Hex ) = %False THEN GOTO TidyUp
    
      FUNCTION = %TRUE
    
      TidyUp:
      CryptReleaseContext hProv, 0 'Our last job
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION HMAC( sText AS STRING, SenderKey AS STRING, WhichHash AS LONG, HashHex AS STRING ) AS LONG
    
      ' Input: sText is the message - text string, binary string, binary file or whatever
      ' Output: HashHex is the HMAC value as a hex string
    
      #REGISTER NONE
      LOCAL hProv, hHash, dummy AS LONG
      DIM ipad       AS STRING
      DIM opad       AS STRING
      DIM hBin AS STRING
    
      hProv = crGetDefaultRSAHandle ' Our first job
      'IF hProv = 0 THEN GOTO TidyUp [COLOR="Red"]REMOVE Ported from another app where other tidying was done[/COLOR]
      If hProv = 0 Then Exit Function
    
      dummy = LEN(SenderKey)
      IF dummy <> 64 THEN
        IF dummy < 64 THEN
          SenderKey = SenderKey + STRING$(64 - dummy, 0) ' Pad with zeros
        ELSE
          IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
          IF TextToHash( SenderKey, hHash ) = %False THEN GOTO TidyUp  ' Hash SenderKey
          IF GetHash( SenderKey, hHash, %Bin) = %False THEN GOTO TidyUp' and get result
          SenderKey = SenderKey + STRING$(64 - LEN( SenderKey ), 0) ' LEN( SenderKey ) depends upon WhichHash
        END IF
      END IF
    
      ipad = SenderKey
      KXorPad( ipad, &h36363636 ) ' ipad becomes SenderKey XOR &h36 for all bytes of SenderKey
      opad = SenderKey
      KXorPad( opad, &h5C5C5C5C ) ' opad becomes SenderKey XOR &h5C for all bytes of SenderKey
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      ' Step 1, Hash ipad + sText
      #IF %File = %True
        IF TextToHash( ipad, hHash) = %False THEN GOTO TidyUp
        IF FileToHash( sText, hHash) = %False THEN GOTO TidyUp
      #ELSE
        IF TextToHash( ipad + sText, hHash) = %False THEN GOTO TidyUp
      #ENDIF
    
      ' Step 2, hBin is the result of hashing ipad + sText - binary string. hHash is destroyed.
      IF GetHash( hBin, hHash, %Bin ) = %False THEN GOTO TidyUp
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      ' Step 3, Hash opad + hBin
      IF TextToHash( opad + hBin, hHash) = %False THEN GOTO TidyUp
    
      ' Step 4, HashHex is the result of TextToHash( opad + hBin ) - hex string. hHash is destroyed.
      IF GetHash( HashHex, Hhash, %Hex ) = %False THEN GOTO TidyUp
    
      FUNCTION = %TRUE
    
      TidyUp:
      CryptReleaseContext hProv, 0 'Our last job
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION H( sText AS STRING, WhichHash AS LONG, HashHex AS STRING ) AS LONG
    
      ' Input: sText is the message - text string, binary string, binary file or whatever
      ' Output: HashHex is the H(ash) value as a hex string
    
      #REGISTER NONE
      LOCAL hProv, hHash AS LONG
    
      hProv = crGetDefaultRSAHandle ' Our first job
      'IF hProv = 0 THEN GOTO TidyUp [COLOR="Red"]REMOVE Ported from another app where other tidying was done[/COLOR]
      If hProv = 0 Then Exit Function
    
      IF CreateHash( hProv, WhichHash, hHash ) = %FALSE THEN GOTO TidyUp
    
      #IF %File = %True
        IF FileToHash( sText, hHash) = %False THEN GOTO TidyUp
      #ELSE
        IF TextToHash( sText, hHash) = %False THEN GOTO TidyUp
      #ENDIF
    
      IF GetHash( HashHex, Hhash, %Hex ) = %False THEN GOTO TidyUp
    
      FUNCTION = %TRUE
    
      TidyUp:
      CryptReleaseContext hProv, 0 'Our last job
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION AXorB( A AS STRING, B AS STRING ) AS LONG
      ' A becomes A Xor B, B remains unchanged
      ' Computation is limited to the length of the shorter string
      ' if the strings are not the same length.
    
      #REGISTER NONE
    
      LOCAL ptrA, ptrB, StringEnd AS DWORD
    
      IF (LEN(A) = 0) OR (LEN(B)=0) THEN
        MSGBOX "Error in AXorB: One or both strings are empty", %MB_TOPMOST, $App
        EXIT FUNCTION
      END IF
    
      ptrA = STRPTR(A) - 1
      ptrB = STRPTR(B) - 1
    
      StringEnd = STRPTR(A) + LEN(A)
      IF LEN(A) > LEN(B) THEN StringEnd = STRPTR(A) + LEN(B)
    
      ! mov esi, ptrA
      ! mov edi, ptrB
      ! mov ecx, StringEnd
    
      StartLoop:
      ! inc esi ' 1st pass is
      ! inc edi ' start of strings
      ! cmp esi, ecx ' Are we at end of strings?
      ! je EndLoop   ' Yes
      ! mov al, [esi]  ' Get byte in A
      ! Xor al, [edi]  ' al becomes byte in A Xor byte in B
      ! mov [esi], al  ' Put byte in A
      ' [edi] is unchanged ie B
      ! jmp StartLoop
    
      EndLoop:
      FUNCTION = %True
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION GetIpadOpad( HXorR AS STRING, ipad AS STRING, opad AS STRING ) AS LONG
    
      DIM lpHXorR AS LONG POINTER
      LOCAL bpIpad, bpOpad AS BYTE POINTER
      LOCAL i AS LONG
      LOCAL row0, row1 AS BYTE PTR
      LOCAL col0, col1, result, BitBase AS LONG
      DIM ReverseBits(7) AS LONG
      DIM BitValue(127) AS LONG
      DIM row(1) AS LONG
    
      ' Chr$(35) + Chr$(99) + Chr$(179) + Chr$(221), for example, in bit form is
      ' 00100011 01100011 10110011 11011101
      '                             ^
      ' BitValue runs from left to right. eg Bitvalue( 25 ) = 1
    
      lpHXorR = STRPTR(hXorR)
    
      FOR i = 7 TO 0 STEP -1
        ReverseBits(7-i) = i
      NEXT
    
      FOR i = 0 TO 127
        BitValue( i ) = BIT( @lpHXorR, (i\8)*8 + ReverseBits( i MOD 8 ) )
      NEXT
    
      ipad = SPACE$(64) : bpIpad = STRPTR(ipad)
      opad = SPACE$(64) : bpOpad = STRPTR(opad)
    
      row(0) = CODEPTR(Table0)
      row(1) = CODEPTR(Table1)
    
      ' Bytes 0 to 62
    
      FOR i = 0 TO 62
    
        BitBase = 2*i
        row0 = row( BitValue( BitBase ) )
        col0 = BitValue( BitBase + 1 ) * 2 + BitValue( BitBase + 2 )
        row1 = row( BitValue( BitBase + 1) )
        col1 = BitValue( BitBase + 2 ) * 2 + BitValue( BitBase + 3 )
        BuildByte(row0, col0, row1, col1)
        @bpIpad = result : INCR bpIpad ' Fill ipad
    
        IF row0 = row( 0 ) THEN row0 = row( 1 ) ELSE row0 = row( 0 )
        IF row1 = row( 0 ) THEN row1 = row( 1 ) ELSE row1 = row( 0 )
        BuildByte(row0, col0, row1, col1)
        @bpOpad = result : INCR bpOpad ' Fill opad
    
      NEXT
    
      ' Byte 63 - we need to use BitValue( 0 ) & BitValue( 1 )
    
      row0 = row( BitValue( 126 ) )
      col0 = BitValue( 127 ) * 2 + BitValue(  0 )
      row1 = row( BitValue( 127 ) )
      col1 = BitValue( 0 ) * 2 + BitValue( 1 )
      BuildByte(row0, col0, row1, col1)
      @bpIpad = result
    
      IF row0 = row( 0 ) THEN row0 = row( 1 ) ELSE row0 = row( 0 )
      IF row1 = row( 0 ) THEN row1 = row( 1 ) ELSE row1 = row( 0 )
      BuildByte(row0, col0, row1, col1)
      @bpOpad = result
    
      EXIT FUNCTION
      ' Substitution box - reversed for little endian format
      Table0:
      ! DD &H0C060906
      Table1:
      ! DD &H0A03050A
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION SystemErrorMessage( BYVAL WinErr AS LONG, API AS STRING, sFunction AS STRING, BYVAL Number AS LONG ) AS STRING
    
      LOCAL pBuffer AS ASCIIZ PTR, ncbBuffer AS DWORD
      DIM sText AS STRING
    
      ncbBuffer = FormatMessage( %FORMAT_MESSAGE_ALLOCATE_BUFFER OR %FORMAT_MESSAGE_FROM_SYSTEM OR %FORMAT_MESSAGE_IGNORE_INSERTS, _
      BYVAL %Null, WinErr, %Null, BYVAL VARPTR(pBuffer), 0, BYVAL %Null )
      IF ncbBuffer THEN
        sText = PEEK$(pBuffer, ncbBuffer)
        sText = REMOVE$(sText, ANY CHR$(13) + CHR$(10))
        FUNCTION = $CRLF + "Error" + FORMAT$(WinErr, " #####") + " during " + API + $CRLF + $CRLF + _
          $DblQuote + sText + $DblQuote + $CRLF + $CRLF + "Location : " + sFunction + IIF$(Number, FORMAT$(Number, " ###"), "")
        LocalFree pBuffer
      ELSE
        FUNCTION = "Unknown error - Code:" + FORMAT$(WinErr, " #####")
      END IF
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    FUNCTION GetASessionKey( WhichHash AS LONG ) AS STRING
      ' CryptGenRandom preffered to RND as RND is not a
      ' crytographically PRNG. In this context RND is also
      ' slower. Function returns a binary string.
    
      LOCAL hProv, WinErr, i AS LONG, hexSize AS DWORD
      DIM hexByte( ) AS BYTE
      DIM strHex AS STRING
    
      hProv = crGetDefaultRSAHandle
      IF hProv = 0 THEN EXIT FUNCTION
    
      IF WhichHash = %CALG_MD5 THEN hexSize = 16 ELSE hexSize = 20
    
      REDIM hexByte( hexSize - 1 ) AS BYTE
    
      IF CryptGenRandom( BYVAL hProv, BYVAL hexSize, hexByte( 0 ) )= 0 THEN
        WinErr = GetLastError : SysErrMsg( "CryptGenRandom", 10 )
        FUNCTION = ""
        EXIT FUNCTION
      END IF
    
      strHex = ""
      FOR i =0 TO hexSize - 1
        strHex = strHex + CHR$( hexByte(i) )
      NEXT
    
      FUNCTION = strHex
    
      CryptReleaseContext hProv, 0
    
    END FUNCTION
    
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Last edited by David Roberts; 28 Mar 2008, 08:15 PM. Reason: Introduced At StrPtr to GetHash

    Comment


    • #3
      D-HMAC.inc

      Code:
      ' Main source code file:  C:\PBWin\Hmac\DHMAC\D_Hmac.bas
      ' Resulting include file: C:\PBWin\Hmac\DHMAC\D_Hmac.inc
      '
      ' Created by incLean v1.27, 03-24-2008, 22:58:00
      ' Press Help-button for some useful information and tips.
      '
      '  51199 lines of include file data read and compared against
      '    676 lines of code in 0.64 seconds.
      '
      '-----------------------------------------------------------------
      ' Equates:  25
      '-----------------------------------------------------------------
      %WINAPI                                         = 1
      %TRUE                                           = 1
      %FALSE                                          = 0
      %NULL                                           = 0
      %GENERIC_READ                                   = &H80000000&
      %FILE_FLAG_SEQUENTIAL_SCAN                      = &H008000000
      %FILE_FLAG_NO_BUFFERING                         = &H020000000
      %OPEN_EXISTING                                  = 3
      %FORMAT_MESSAGE_ALLOCATE_BUFFER                 = &H100
      %FORMAT_MESSAGE_IGNORE_INSERTS                  = &H200
      %FORMAT_MESSAGE_FROM_SYSTEM                     = &H1000
      %MB_ICONHAND                                    = &H00000010&
      %MB_ICONERROR                                   = %MB_ICONHAND
      %MB_TOPMOST                                     = &H00040000&
      $MS_DEF_PROV                                    = "Microsoft Base Cryptographic Provider v1.0"
      %CRYPT_VERIFYCONTEXT                            = &hF0000000
      %HP_HASHVAL                                     = &h0002
      %HP_HASHSIZE                                    = &h0004
      %ALG_SID_MD5                                    = 3
      %ALG_SID_SHA1                                   = 4
      %PROV_RSA_FULL                                  = 1
      %ALG_CLASS_HASH                                 = 32768     '(4 << 13)        '32768
      %ALG_TYPE_ANY                                   = 0
      %CALG_MD5                                       = %ALG_CLASS_HASH OR %ALG_TYPE_ANY OR %ALG_SID_MD5
      %CALG_SHA1                                      = %ALG_CLASS_HASH OR %ALG_TYPE_ANY OR %ALG_SID_SHA1
      
      '-----------------------------------------------------------------
      ' TYPE and UNION structures:  2
      '-----------------------------------------------------------------
      TYPE OVERLAPPED
        Internal AS DWORD
        InternalHigh AS DWORD
        offset AS DWORD
        OffsetHigh AS DWORD
        hEvent AS DWORD
      END TYPE
      
      TYPE SECURITY_ATTRIBUTES
        nLength AS DWORD
        lpSecurityDescriptor AS LONG
        bInheritHandle AS LONG
      END TYPE
      
      '-----------------------------------------------------------------
      ' Declared Functions:  16
      '-----------------------------------------------------------------
      DECLARE FUNCTION CloseHandle LIB "KERNEL32.DLL" ALIAS "CloseHandle" (BYVAL hObject AS DWORD) AS LONG
      DECLARE FUNCTION CreateFile LIB "KERNEL32.DLL" ALIAS "CreateFileA" (lpFileName AS ASCIIZ, BYVAL dwDesiredAccess AS DWORD, BYVAL dwShareMode AS DWORD, lpSecurityAttributes AS SECURITY_ATTRIBUTES, BYVAL dwCreationDisposition AS DWORD, _
                       BYVAL dwFlagsAndAttributes AS DWORD, BYVAL hTemplateFile AS DWORD) AS DWORD
      DECLARE FUNCTION CryptAcquireContext LIB "advapi32.dll" ALIAS "CryptAcquireContextA" _
            (  hCryptProv AS LONG, zContainer AS ASCIIZ, zProvider AS ASCIIZ, _
               BYVAL dwProvType AS DWORD, BYVAL dwFlags AS DWORD ) AS LONG
      DECLARE FUNCTION CryptCreateHash LIB "advapi32.dll" ALIAS "CryptCreateHash" _
            (  BYVAL hProv AS LONG, BYVAL iAlgID AS LONG, BYVAL hKey AS LONG, _
               BYVAL dwFlags AS DWORD, hHash AS LONG ) AS LONG
      DECLARE FUNCTION CryptDestroyHash LIB "advapi32.dll" ALIAS "CryptDestroyHash" _
            (  BYVAL hHash AS LONG ) AS LONG
      DECLARE FUNCTION CryptGenRandom LIB "advapi32.dll" ALIAS "CryptGenRandom" _
            ( BYVAL hProv AS LONG, BYVAL dwLen AS DWORD, pbBuffer AS BYTE) AS LONG
      DECLARE FUNCTION CryptGetHashParam LIB "advapi32.dll" ALIAS "CryptGetHashParam" _
            (  BYVAL hHash AS LONG, BYVAL dwParam AS DWORD, pbData AS ANY, _
               pdwDataLen AS LONG, BYVAL dwFlags AS LONG ) AS LONG
      DECLARE FUNCTION CryptHashData LIB "advapi32.dll" ALIAS "CryptHashData" _
            (  BYVAL hHash AS LONG, pbData AS ANY, BYVAL dwDatalen AS DWORD, _
               BYVAL dwFlags AS LONG ) AS LONG
      DECLARE FUNCTION CryptReleaseContext LIB "advapi32.dll" ALIAS "CryptReleaseContext" _
            (  BYVAL hCryptProv AS LONG, BYVAL dwFlags AS DWORD ) AS LONG
      DECLARE FUNCTION FormatMessage LIB "KERNEL32.DLL" ALIAS "FormatMessageA" (BYVAL dwFlags AS DWORD, lpSource AS ANY, BYVAL dwMessageId AS DWORD, BYVAL dwLanguageId AS DWORD, lpBuffer AS ASCIIZ, BYVAL nSize AS DWORD, Arguments AS ANY) AS DWORD
      DECLARE FUNCTION GetLastError LIB "KERNEL32.DLL" ALIAS "GetLastError" () AS LONG
      DECLARE FUNCTION LocalFree LIB "KERNEL32.DLL" ALIAS "LocalFree" (BYVAL hMem AS DWORD) AS LONG
      DECLARE FUNCTION MessageBox LIB "USER32.DLL" ALIAS "MessageBoxA" (BYVAL hWnd AS DWORD, lpText AS ASCIIZ, lpCaption AS ASCIIZ, BYVAL dwType AS DWORD) AS LONG
      DECLARE FUNCTION QueryPerformanceCounter LIB "KERNEL32.DLL" ALIAS "QueryPerformanceCounter" (lpPerformanceCount AS QUAD) AS LONG
      DECLARE FUNCTION QueryPerformanceFrequency LIB "KERNEL32.DLL" ALIAS "QueryPerformanceFrequency" (lpFrequency AS QUAD) AS LONG
      DECLARE FUNCTION ReadFile LIB "KERNEL32.DLL" ALIAS "ReadFile" (BYVAL hFile AS DWORD, lpBuffer AS ANY, BYVAL nNumberOfBytesToRead AS DWORD, lpNumberOfBytesRead AS DWORD, lpOverlapped AS OVERLAPPED) AS LONG

      Comment

      Working...
      X