Announcement

Collapse
No announcement yet.

SHA 256 woes

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

  • SHA 256 woes

    This call measures the length of the hash and places it in cchBase64:

    Code:
    CryptBinaryToStringA(pbBinary,  cbBinary, _
                                %CRYPT_STRING_BASE64 or %CRYPT_STRING_NOCRLF, _
                                "", cchBase64)
    results in cchBase64 = 345. The Base64 hex string is 44 characters long. MSDN says that the length returned is in TChars, which are the same as Wchars in a Unicode environment. But using CryptBinaryToStringA rather than CryptBinaryToStringW implies a non-Unicode environment.

    This returns the hash - the value supplied for cchbase64 must be the same as used in the previous call to CryptBinaryToStringA, or it fails

    Code:
    CryptBinaryToStringA( pbBinary,  cbBinary, _
                                %CRYPT_STRING_BASE64 or %CRYPT_STRING_NOCRLF, _
                                byval strptr(strBase64), cchbase64)

    The buffer into which it is copied (strBase64) contains first the hash all but for the last character, then the remainder of the buffer is filled with "A"s, and the last character of the buffer is overwritten with the last character of the hash.

    Any suggestions?

    Here's the whole of my test code:

    Code:
     #debug display
    #compile exe
    #dim all
    #option ansiapi
    
    #include "win32api.inc"
    global buf as asciz * 256
    
    function BinaryToBase64(byval pbBinary as dword, byval cbBinary as dword) as string
    
        local cchBase64 as dword
        local strbase64 as string
        local pszbase64 as dword
    
        if CryptBinaryToStringA(pbBinary,  cbBinary, _
                                %CRYPT_STRING_BASE64 or %CRYPT_STRING_NOCRLF, _
                                "", cchBase64) = 0 then
            ? "BinaryToBase64 error 1"
            exit function
        end if
        strbase64 = string$(cchBase64+1, 0)
        cchbase64 += 1
        ? str$(cchbase64)
         if CryptBinaryToStringA( pbBinary,  cbBinary, _
                                 %CRYPT_STRING_BASE64 or %CRYPT_STRING_NOCRLF, _
                                 byval strptr(strBase64), cchbase64) = 0 then
            ? "BinaryToBase64 error 2: "
            exit function
        end if
        function = strBase64
        ? strbase64
    end function
    
    function CreateHash (tohash as string) as string
          local hprov as dword
          local hash as dword
          local sz, winerr  as long
          local stemp as string
    
        if CryptAcquireContext (hprov, "", "", %PROV_RSA_AES, 0) then
            ? "CreateHash: CryptAcquireContext succeeded 1"
        else
            if CryptAcquireContext(hProv, byval 0, byval 0, %PROV_RSA_AES, %CRYPT_VERIFYCONTEXT) = 0 then
                  err = getlasterror
                  ? "CreateHash error 1: " + error$
                  exit function
            else
                  ? "CreateHash: CryptAcquireContext succeeded 2"
            end if
        end if
        if CryptCreateHash(hProv, %CALG_SHA_256, 0, 0, byval varptr(hash)) = 0 then
            WinErr = GetLastError
            ?"CreateHash error 6:" + hex$(winerr)
            exit function
        end if
            sz = len(tohash)
            CryptHashData(hash, byval strptr(tohash), sz, 0)
                ZeroMemory(varptr(Buf), sizeof(Buf))
                CryptGetHashParam(hash, %HP_HASHVAL, byval varptr(buf), sizeof(buf), 0)
            if hash then CryptDestroyHash(hash)
        if CryptReleaseContext(hProv, 0) = 0 then
            err = getlasterror
            ? "CreateHash error 5: " + error$
            exit function
        end if
        stemp = BinaryToBase64(varptr(Buf), sizeof(Buf))
        function = stemp
    end function
    '
    function pbmain () as long
        local stext, hash as string
    
        ? str$(len( "5/NK+1ZAwTjzTY1PjZm0xcPRDf6KMQhmE4SVQnPOQ3M="))
        stext = "doublecheck"
        hash = CreateHash(stext)
        ? "input = " + stext + $crlf + _
        "result should be 5/NK+1ZAwTjzTY1PjZm0xcPRDf6KMQhmE4SVQnPOQ3M="  + $crlf + _
        " actual result : " + hash + $crlf
    end function

  • #2
    global buf as asciz * 256

    1) A "Z" string of 256 only holds 255 bytes. The last is the terminating zero byte. If shorter -
    2) A "Z" string terminates at first zero byte. Zero bytes are as likely to occur as any other value. This cuts off any following bytes of data.

    Therefore "Z" strings are absolutely useless for hashes and encryption. Use fixed length strings or dynamic strings without changing the length (then the memory location stays the same).

    Cheers,
    Dale

    Comment


    • #3
      My humble opinion...
      Code:
      'Replace
      CryptGetHashParam(hash, %HP_HASHVAL, BYVAL VARPTR(Buf), SIZEOF(Buf), 0)
      'with
      LOCAL dwDataLen AS DWORD
      dwDataLen = SIZEOF(Buf)
      CryptGetHashParam(hash, %HP_HASHVAL, BYVAL VARPTR(Buf), dwDataLen, 0)
      
      'Replace 
      sTemp = BinaryToBase64(VARPTR(Buf), SIZEOF(Buf))
      'with
      sTemp = BinaryToBase64(VARPTR(Buf), dwDataLen)

      Comment


      • #4
        Pierre beat me to it. You need to get the hash length. Here's another way:

        Code:
         #DEBUG DISPLAY
        #COMPILE EXE
        #DIM ALL
        #OPTION ANSIAPI
        
        #INCLUDE "win32api.inc"
        GLOBAL buf AS STRING * 256
        
        '==== Next line added
        GLOBAL buflen AS DWORD
        
        FUNCTION BinaryToBase64(BYVAL pbBinary AS DWORD, BYVAL cbBinary AS DWORD) AS STRING
        
            LOCAL cchBase64 AS DWORD
            LOCAL strbase64 AS STRING
            LOCAL pszbase64 AS DWORD
        
            IF CryptBinaryToStringA(pbBinary,  cbBinary, _
                                    %CRYPT_STRING_BASE64 OR %CRYPT_STRING_NOCRLF, _
                                    "", cchBase64) = 0 THEN
                ? "BinaryToBase64 error 1"
                EXIT FUNCTION
            END IF
            strbase64 = STRING$(cchBase64+1, 0)
            cchbase64 += 1
            ? STR$(cchbase64)
             IF CryptBinaryToStringA( pbBinary,  cbBinary, _
                                     %CRYPT_STRING_BASE64 OR %CRYPT_STRING_NOCRLF, _
                                     BYVAL STRPTR(strBase64), cchbase64) = 0 THEN
                ? "BinaryToBase64 error 2: "
                EXIT FUNCTION
            END IF
            FUNCTION = strBase64
            ? strbase64
        END FUNCTION
        
        FUNCTION CreateHash (tohash AS STRING) AS STRING
              LOCAL hprov AS DWORD
              LOCAL hash AS DWORD
              LOCAL sz, winerr  AS LONG
              LOCAL stemp AS STRING
        
            IF CryptAcquireContext (hprov, "", "", %PROV_RSA_AES, 0) THEN
                ? "CreateHash: CryptAcquireContext succeeded 1"
            ELSE
                IF CryptAcquireContext(hProv, BYVAL 0, BYVAL 0, %PROV_RSA_AES, %CRYPT_VERIFYCONTEXT) = 0 THEN
                      ERR = getlasterror
                      ? "CreateHash error 1: " + ERROR$
                      EXIT FUNCTION
                ELSE
                      ? "CreateHash: CryptAcquireContext succeeded 2"
                END IF
            END IF
        
            IF CryptCreateHash(hProv, %CALG_SHA_256, 0, 0, BYVAL VARPTR(hash)) = 0 THEN
                WinErr = GetLastError
                ?"CreateHash error 6:" + HEX$(winerr)
                EXIT FUNCTION
            END IF
                sz = LEN(tohash)
                CryptHashData(hash, BYVAL STRPTR(tohash), sz, 0)
                    ZeroMemory(VARPTR(Buf), SIZEOF(Buf))
        
        '===next line added =====
                    CryptGetHashParam(hash, %HP_HASHSIZE, BYVAL VARPTR(buflen), SIZEOF(buf), 0)
        
        
                    CryptGetHashParam(hash, %HP_HASHVAL, BYVAL VARPTR(buf), SIZEOF(buf), 0)
                IF hash THEN CryptDestroyHash(hash)
            IF CryptReleaseContext(hProv, 0) = 0 THEN
                ERR = getlasterror
                ? "CreateHash error 5: " + ERROR$
                EXIT FUNCTION
            END IF
        
        '====== next line changed to use buflen
            stemp = BinaryToBase64(VARPTR(Buf), buflen)
        
        
            FUNCTION = stemp
        END FUNCTION
        '
        FUNCTION PBMAIN () AS LONG
            LOCAL stext, hash AS STRING
        
         '   ? str$(len( "5/NK+1ZAwTjzTY1PjZm0xcPRDf6KMQhmE4SVQnPOQ3M="))
            stext = "doublecheck"
            hash = CreateHash(stext)
            ? "input = " + stext + $CRLF + _
            "result should be 5/NK+1ZAwTjzTY1PjZm0xcPRDf6KMQhmE4SVQnPOQ3M="  + $CRLF + _
            " actual result : " + hash + $CRLF
        END FUNCTION

        Comment


        • #5
          The output of a SHA-256 hash is 256 bits regardless of existance or position of any NUL bytes.

          256 bits is 32 bytes. A hex string of that would be 64 characters, Each of 0 through F.

          So I can not figure out why the SIZEOF() and/or LEN() are needed. ((possibly input to be hassed length.))
          Dale

          Comment


          • #6
            Originally posted by Chris Holbrook View Post
            This call measures the length of the hash and places it in cchBase64:

            The buffer into which it is copied (strBase64) contains first the hash all but for the last character, then the remainder of the buffer is filled with "A"s, and the last character of the buffer is overwritten with the last character of the hash.
            A correction to this.

            strbase64 doesn't "contain the hash", it contains the Base64 encoding of the binary hash.
            The "=" character is a B64 padding character, It is not part of the Hash, but is added to the end of the string by the BinaryToBase64 function . (A B64 encoded string must always be a multiple of 4 characters, so there can be up to three "="'s at the end.

            The string of A's is the B64 encoding of the bytes after the Hash (probably Nuls)

            Comment


            • #7
              Originally posted by Dale Yarker View Post
              The output of a SHA-256 hash is 256 bits regardless of existance or position of any NUL bytes.

              256 bits is 32 bytes. A hex string of that would be 64 characters, Each of 0 through F.

              So I can not figure out why the SIZEOF() and/or LEN() are needed. ((possibly input to be hassed length.))
              Because CryptCreateHash doesn't just do SHA-256. Replace "%CALG_SHA_256" with a different constant and the same function will return a different sized hash.

              (And Hex strings don't come into it. The binary hash value is converted to a base64 encoded string, not a Hex representation)

              Comment


              • #8

                Note that there is no need to increase cchbase64. Another flavor...

                Code:
                #COMPILE EXE
                #DIM ALL
                #INCLUDE "Win32Api.inc"
                
                $AppName = "Crypt"
                '_____________________________________________________________________________
                
                FUNCTION BinaryToBase64(pbBinary AS DWORD, BYVAL cbBinary AS DWORD) AS STRING
                 LOCAL strbase64 AS STRING
                 LOCAL cchBase64 AS DWORD
                 LOCAL LastError AS LONG
                
                 IF CryptBinaryToStringA(pbBinary, cbBinary, %CRYPT_STRING_BASE64 OR _ 'Get lenght
                                         %CRYPT_STRING_NOCRLF, BYVAL %NULL, cchBase64) THEN
                   strbase64 = NUL$(cchBase64)
                   IF CryptBinaryToStringA(pbBinary, cbBinary, %CRYPT_STRING_BASE64 OR _ 'Base64 encode
                                           %CRYPT_STRING_NOCRLF, BYVAL STRPTR(strBase64), cchbase64) THEN
                      FUNCTION = strBase64
                   END IF
                 END IF
                
                 LastError = GetLastError
                 IF LastError THEN MessageBox(%HWND_DESKTOP, "BinaryToBase64 error" & STR$(LastError), _
                                              $AppName, %MB_ICONERROR OR %MB_OK OR %MB_TOPMOST)
                END FUNCTION
                '_____________________________________________________________________________
                
                FUNCTION CreateHash (sData AS STRING) AS STRING
                 LOCAL sByteData AS STRING
                 LOCAL hprov     AS DWORD
                 LOCAL hHash     AS DWORD
                 LOCAL dwDataLen AS DWORD
                 LOCAL LastError AS LONG
                
                 IF (CryptAcquireContext(hprov, "", "", %PROV_RSA_AES, 0) OR _ 'Next line api will be called only if first fail
                     CryptAcquireContext(hProv, BYVAL 0, BYVAL 0, %PROV_RSA_AES, %CRYPT_VERIFYCONTEXT)) THEN
                   IF CryptCreateHash(hProv, %CALG_SHA_256, 0, 0, BYVAL VARPTR(hHash)) THEN 'Initiate data hashing
                     IF CryptHashData(hHash, BYVAL STRPTR(sData), LEN(sData), 0) THEN 'Compute cryptographic hash
                       IF CryptGetHashParam(hHash, %HP_HASHVAL, 0, dwDataLen, 0) THEN 'Get sByteData lenght
                         sByteData = NUL$(dwDataLen)
                         IF CryptGetHashParam(hHash, %HP_HASHVAL, BYVAL STRPTR(sByteData), dwDataLen, 0) THEN 'Get data
                           IF CryptDestroyHash(hHash) THEN 'Clean up
                             IF CryptReleaseContext(hProv, 0) THEN 'Release handle
                               FUNCTION = BinaryToBase64(STRPTR(sByteData), dwDataLen)
                             END IF
                           END IF
                         END IF
                       END IF
                     END IF
                   END IF
                 END IF
                
                 LastError = GetLastError
                 IF LastError THEN MessageBox(%HWND_DESKTOP, "CreateHash error" & STR$(LastError), _
                                              $AppName, %MB_ICONERROR OR %MB_OK OR %MB_TOPMOST)
                END FUNCTION
                '_____________________________________________________________________________
                
                FUNCTION PBMAIN () AS LONG
                 LOCAL sData AS STRING
                 LOCAL sHash AS STRING
                
                 sData = "doublecheck"
                 sHash = CreateHash(sData)
                
                 MessageBox(%HWND_DESKTOP, "Input is """ & sData & """" & $CRLF & $CRLF & _
                 "Result should be " & $TAB & "5/NK+1ZAwTjzTY1PjZm0xcPRDf6KMQhmE4SVQnPOQ3M=" & $CRLF & $CRLF & _
                 "Actual result is " & $TAB & sHash, $AppName, %MB_OK OR %MB_TOPMOST)
                
                END FUNCTION
                '_____________________________________________________________________________
                '

                Comment


                • #9
                  Thank you very much Dale, Pierre and Stuart for excellent points and great examples.

                  The code started as a C++ example on MSDN which I converted with the help of examples in these forums. Plus my schoolboy errors, it's amazing how fast coding skill evaporates with lack of use.

                  Great to be back on the learning curve!




                  Comment


                  • #10
                    Replace "%CALG_SHA_256" with a different constant and the same function will return a different sized hash.
                    Yes, but the different sized hash needs whatever it's size is, and not be corupted by a valid nul in a "Z" string.

                    The specific subject here is SHA-256. The general point you apparently can't see is "do not use "Z" strings where imbedded nuls can exist.

                    Hex strings contain characters, so a nul is represented by "00" and would be unaffected by being in a "Z" string. The raw hash is affected.
                    Dale

                    Comment


                    • #11
                      Originally posted by Dale Yarker View Post
                      ... The general point you apparently can't see is "do not use "Z" strings where imbedded nuls can exist....
                      I did see that point. I just forgot to comment the change to "GLOBAL buf AS STRING * 256" in my example code.

                      Comment


                      • #12
                        Hi Pierre

                        I got a compilation error when compiling your code at post #8

                        Code:
                        Error 426 in   c:\PB\SHA 256\SHA256.bas(41:049):  Variable expected
                          Line 41:        IF CryptGetHashParam(hHash, %HP_HASHVAL, 0, dwDataLen, 0) THEN 'Get sByteData lenght
                        I have used Jose Roca's includes and PBWin10

                        Comment


                        • #13
                          Hi Anne,
                          There's a difference in the declarations in PB / JR includes
                          Try this..
                          Code:
                          #COMPILE EXE
                          #DIM All
                          %USEPBDECL=1               ' < if using JR includes
                          #INCLUDE "Win32Api.inc"
                          Rgds, Dave

                          Comment


                          • #14
                            Thanks so much, Dave

                            Comment


                            • #15
                              For hashing, you may want to consider using the M$ CNG API. This is a hashing function that I'm using in prototype apps. It may be a bit overkill but some of these multi-threaded apps call it up to 50K times per second. It uses the PB includes and hashes whatever data you pass a pointer to.

                              Code:
                              '==================================================================================================
                              ' cngSHA256 - Return 32-byte hash of input block of data.
                              '
                              ' About 2M/sec. Using THREADSAFE (semaphore) causes mimimal perf hit but is easy to implement.
                              '==================================================================================================
                              FUNCTION cngSHA256 ( _
                                  BYVAL pbIn    AS LONG, _  'IN: Pointer to buffer with data to hash
                                  BYVAL inSize  AS LONG, _  'IN: Length of data in buffer to hash
                                  BYVAL pbOut   AS LONG _   'IN: Pointer to 32-byte buffer to write hash to
                                  ) THREADSAFE AS LONG      'RET: Err val or zero for success.
                              
                                STATIC hProv   AS LONG  ' Persistent handle for the BCrypt hash provider.
                                STATIC keySize AS LONG
                                STATIC keyPtr  AS LONG
                                REGISTER hKey  AS LONG  ' New key creted each time function called.
                                STATIC hKeyMem AS LONG
                              
                                '-- Test threaded BCrypt hash provider handle. If no handle then open an algorithm provider for
                                '   random generation and initialize the provider. These two BCrypt functions should never fail.
                                IF ISFALSE(hProv) THEN
                                  BCryptOpenAlgorithmProvider(hProv, $$BCRYPT_SHA256_ALGORITHM, $$NUL, 0)
                                  BCryptGetProperty ( _
                                      hProv, _
                                      $$BCRYPT_OBJECT_LENGTH, _
                                      VARPTR(keySize), _
                                      4, _
                                      %NULL, _
                                      0 _
                                      )
                                  '-- Memory allocation should not fail but test anyway. Perhaps severe memory fragmentation
                                  '   could cause issues - which means that there would probably be other problems in the app
                                  '   as well. Destroy the CNG provider if this occurs. Otherwise, we have a provider handle
                                  '   and will not do this code again.
                                  GLOBALMEM ALLOC keySize TO hKeyMem
                                  IF hKeyMem = 0 THEN
                                    BCryptCloseAlgorithmProvider hProv, 0
                                    FUNCTION = %CNG_MALLOC
                                  ELSE
                                    GLOBALMEM LOCK hKeyMem TO keyPtr ' Should never fail
                                  END IF
                                END IF
                              
                                '-- Create a new hash key every time that we call the function. Re-use the same global memeory
                                '   that was allocated for this thread each time using the same global memory that was
                                '   allocated the first time called. Doing this should improve performance and help to mitigate
                                '   memory fragmentation. It would be good to only create the key once but the key generated
                                '   by BCryptCreateHash seems to be destroyed once it has been used to hash data. This should
                                '   never fail.
                                BCryptCreateHash ( _
                                    hProv, _      ' This will always be valid (above)
                                    hKey, _       ' This is generated now using default values (below)
                                    keyPtr, _     ' Let Windows allocate hash object memory (prob 286 bytes long)
                                    keySize, _    ' Zero length since Windows handles allocating memory
                                    %NULL, _      ' Any secrets must be part of the data to be hashed
                                    0, _          ' No secret so no length
                                    0 _           ' No flags used since they would only apply to WIN10
                                    )
                              
                                '-- Hash input data with our one-time key. The only reason that this should fail is if the
                                '   function args are bad (null buffer pointer or length). This will NOT catch bad memory
                                '   locations which will probably cause a GPF!
                                IF BCryptHashData ( _
                                    hKey, _
                                    pbIn, _               ' Pointer to buffer with data to hash
                                    inSize, _             ' Length of data in the buffer to hash
                                    0 _                   ' No flags
                                    ) THEN
                                  FUNCTION = %CNG_BADPARAM
                                  EXIT FUNCTION
                                END IF
                              
                                '-- Copy hash to output buffer. The only reason that this should fail is if the ptr to the
                                '   output buffer is NULL. Note the output buffer MUST BE AT LEAST 32 bytes (256 bits) long.
                                '   If the pointer or buffer size are incorrect, you will probably GPF.
                                IF BCryptFinishHash ( _
                                    hKey, _
                                    pbOut, _              ' Pointer to buffer to write to
                                    32, _                 ' Only need 256 bits to write to
                                    0 _                   ' No flags
                                    ) THEN
                                  FUNCTION = %CNG_BADPARAM
                                END IF
                              
                              END FUNCTION  ' cngSHA256

                              Comment


                              • #16
                                Originally posted by Jerry Wilson View Post
                                For hashing, you may want to consider using the M$ CNG API. This is a hashing function that I'm using in prototype apps. It may be a bit overkill but some of these multi-threaded apps call it up to 50K times per second. It uses the PB includes and hashes whatever data you pass a pointer to.
                                Good advice. As MS says about CryptoAPI:

                                Important This API is deprecated. New and existing software should start using Cryptography Next Generation APIs. Microsoft may remove this API in future releases.

                                Comment

                                Working...
                                X