Why, after using 'CryptSetHashParam', can I no longer add data to my MD5 hash object?

0

I am trying to use the Microsoft 'Crypt...' functions to generate an MD5 hash key from the data that is added to the hash object. I am also trying to use the 'CryptSetHashParam' to set the hash object to a particular hash value before adding data to it.

According to the Microsoft documentation (if I am interpreting it correctly), you should be able to do this by creating a duplicate hash of the original object, use the 'CryptGetHashParam' function to retrieve the hash size then use 'CryptSetHashParam' on the original object to set the hash value accordingly. I am aware that after using 'CryptGetHashParam' you are unable to add additional data to a hash object (which is why I thought you needed to create a duplicate), but I can't add data to either the original hash object or the duplicate hash object after using either 'CryptGetHashParam' (as expected), or 'CryptSetHashParam' (which I didn't expect).

Below are code extracts of the class I am writing and an example of how I am using the class functions:

The result I get after running the code is:

"AddDataToHash function failed - Errorcode: 2148073484.", which translates to: "Hash not valid for use in specified state.".

I've tried many different ways to try and get this working as intended, but the result is always the same. I accept that I am doing something wrong, but I can't see what it is I'm doing wrong. Any ideas please?

CLASS CONSTRUCTOR INITIALISATION.

CAuthentication::CAuthentication()

{

    m_dwLastError = ERROR_SUCCESS;

    m_hCryptProv = NULL;

    m_hHash = NULL;

    m_hDuplicateHash = NULL;

    if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))

    {
        m_dwLastError = GetLastError();

        if (m_dwLastError == 0x80090016 )
        {
            if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) 
            {
                 m_dwLastError = GetLastError();

                 m_hCryptProv = NULL;
            }
         }
    }

    if(!CryptCreateHash(m_hCryptProv, CALG_MD5, 0, 0, &m_hHash))
    {
        m_dwLastError = GetLastError();

        m_hHash = NULL;
    }
}

FUNCTION USED TO SET THE HASH VALUE OF THE HASH OBJECT.

bool CAuthentication::SetHashKeyString(char* pszKeyBuffer)

{

    bool bHashStringSet = false;

    DWORD dwHashSize = 0;
    DWORD dwHashLen = sizeof(DWORD);

    BYTE byHash[DIGITAL_SIGNATURE_LENGTH / 2]={0};

    if(pszKeyBuffer != NULL && strlen(pszKeyBuffer) == DIGITAL_SIGNATURE_LENGTH)
    {
        if(CryptDuplicateHash(m_hHash, NULL, 0, &m_hDuplicateHash))
        {
            if(CryptGetHashParam(m_hDuplicateHash, HP_HASHSIZE, reinterpret_cast<BYTE*>(&dwHashSize), &dwHashLen, 0))
            {        
                if (dwHashSize == DIGITAL_SIGNATURE_LENGTH / 2)
                {
                    char*pPtr = pszKeyBuffer;

                    ULONG ulTempVal = 0;

                    for(ULONG ulIdx = 0; ulIdx < dwHashSize; ulIdx++)
                    {
                        sscanf(pPtr, "%02X", &ulTempVal);

                        byHash[ulIdx] = static_cast<BYTE>(ulTempVal);

                        pPtr+= 2;
                    }

                    if(CryptSetHashParam(m_hHash, HP_HASHVAL, &byHash[0], 0)) 
                    {
                        bHashStringSet = true;
                    }
                    else
                    {
                        pszKeyBuffer = "";
                        m_dwLastError = GetLastError();
                    }
                }
            }
            else
            {
                m_dwLastError = GetLastError();
            }
        }
        else
        {
            m_dwLastError = GetLastError();
        }
    }

    if(m_hDuplicateHash != NULL)
    {
        CryptDestroyHash(m_hDuplicateHash);
    }  

    return bHashStringSet;
}

FUNCTION USED TO ADD DATA FOR HASHING.

bool CAuthentication::AddDataToHash(BYTE* pbyHashBuffer, ULONG ulLength)

{

    bool bHashDataAdded = false;

    if(CryptHashData(m_hHash, pbyHashBuffer, ulLength, 0))
    {
        bHashDataAdded = true;
    }
    else
    {
        m_dwLastError = GetLastError();
    }

    return bHashDataAdded;
}

MAIN FUNCTION CLASS USAGE:

CAuthentication auth;

.....

auth.SetHashKeyString("0DD72A4F2B5FD48EF70B775BEDBCA14C");

.....

if(!auth.AddDataToHash(pbyHashBuffer, ulDataLen))

{

    TRACE("CryptHashData function failed - Errorcode: %lu.\n", auth.GetAuthError());
}
c++
cryptography
md5
hash
asked on Stack Overflow Feb 9, 2010 by Graham Schofield • edited Sep 6, 2013 by Julien Roncaglia

1 Answer

1

You can't do it because it doesn't make any sense. CryptGetHashParam with the HP_HASHVAL option finalizes the hash, so there is no way to add data to it. If you want to "fork" the hash so that you can finalize it at some point as well as add data to it, you must duplicate the hash object prior to finalizing. Then you add the data to one of the hash objects and finalize the other. For example, you might do this if you wanted record a cumulative hash after every 1024 bytes of a data stream. You should not call CryptSetHashParam on the hash object that you are continuing to add data to.

CryptSetHashParam with the HP_HASHVAL option is a brutal hack to overcome a limitation in the CryptoAPI. The CryptoAPI will only sign a hash object, so if you want to sign some data that might have been hashed or generated outside of CAPI, you have to "jam" it into a hash object.

EDIT:
Based on your comment, I think you are looking for a way to serialize the hash object. I cannot find any evidence that CryptoAPI supports this. There are alternatives, however, that are basically variants of my "1024 bytes" example above. If you are hashing a sequence of files, you could simply compute and save the hash of each file. If you really need to boil it down to one value, then you can compute a modified hash where the first piece of data you hash for file i is the finalized hash for files 0, 1, 2, ..., i-1. So:
H-1 = empty,
Hi = MD5 (Hi-1 || filei)

As you go along, you can save the last successfully computed Hi value. In case of interruption, you can restart at file i+1. Note that, like any message digest, the above is completely sensitive to both order and content. This is something to consider on a dynamically changing file system. If files can be added or changed during the hashing operation, the meaning of the hash value will be affected. It might be rendered meaningless. You might want to be certain that both the sequence and content of the files you are hashing is frozen during the entire duration of the hash.

answered on Stack Overflow Feb 10, 2010 by President James K. Polk • edited Feb 11, 2010 by President James K. Polk

User contributions licensed under CC BY-SA 3.0