ShaderOp.com

ShaderOp.com

Hi there. I have moved to a new website at Mhmmd.org. I'm no longer updating this one, but I'm keeping it around so the Internet wouldn't break. See you there.

RAII by Example: Implementing GenerateSha1Hash

In the previous installment of the SHA-1—saga I didn’t provide an implementation for the function GenerateSha1Hash. So I thought it might be a good idea to milk the subject even more by coding it up and using the opportunity to demonstrate how the Resource Acquisition Is Initialization (or RAII, not be confused with Rai) idiom leads to code that is more maintainable and fault-tolerant.

If I were doing something more involved or required cross-platform portability, I would have been better off with the Crypto++ library or something similar. But instead I’ll be using the Cryptography functions from the Win32 API because they rely on manual acquisition and release of resources through handles, which is a better fit for demonstrating RAII in action.

I ended up using the following functions:

This is my initial, RAII-less implementation:

#include "GenerateSha1Hash.h"

#include <windows.h>

int GenerateSha1Hash(const char* data, int8_t** hash, int hashSize)
{
    if (hashSize == 0)
        return 0;

    HCRYPTPROV hCryptProvider = NULL;
    HCRYPTHASH hHash = NULL;

    BOOL status = ::CryptAcquireContext
        ( &hCryptProvider
        , NULL
        , NULL
        , PROV_RSA_FULL
        , 0
        );
    if (status == false)
        return -1;
    status = ::CryptCreateHash(hCryptProvider, CALG_SHA1, 0, 0, &hHash);
    if (status == false)
    {
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

    status = ::CryptHashData
        ( hHash
        , reinterpret_cast<const BYTE*>(data)
        , strlen(data) + 1
        , 0
        );

    if (status == false)
    {
        ::CryptDestroyHash(hHash);
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

    DWORD hashBufferSize = 0;
    DWORD hashBufferSizeSize = sizeof(DWORD)/sizeof(BYTE);

    status = ::CryptGetHashParam
        ( hHash
        , HP_HASHSIZE
        , reinterpret_cast<BYTE*>(&hashBufferSize)
        , &hashBufferSizeSize
        , 0
        );
    if (status == false)
    {
        ::CryptDestroyHash(hHash);
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

    BYTE* hashBuffer = new BYTE[hashBufferSize];
    status = ::CryptGetHashParam
        ( hHash
        , HP_HASHVAL
        , hashBuffer
        , &hashBufferSize
        , 0
        );
    if (status == false)
    {
        delete hashBuffer;
        ::CryptDestroyHash(hHash);
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

    int numberOfBytesToCopy = (static_cast<int>(hashBufferSize) < hashSize)?
                              hashBufferSize : hashSize;
    for (int i = 0; i < numberOfBytesToCopy; ++i)
        (*hash)[i] = hashBuffer[i];

    delete hashBuffer;
    ::CryptDestroyHash(hHash);
    ::CryptReleaseContext(hCryptProvider, 0);

    return numberOfBytesToCopy;
}

The mechanics are not that relevant to our example, and anyone could arrive to a similar implementation by reading the MSDN documentation at the links to above. But here are the basic, broad strokes:

  1. Ask the Win32 API for a CryptContext.
  2. Ask the CryptContext for a CryptHash that can produce SHA-1 hashes.
  3. Pass the data to be hashed to the CryptHash.
  4. Ask the CryptHash for the number of bytes it will need to store the hash.
  5. Pass a buffer to the CryptHash and ask it to store the hash in the buffer.
  6. Release all resources.

Which is simple enough. What complicates the code is this refrain:

    if (status == false)
    {
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

What later repeats and grows into this:

    if (status == false)
    {
        ::CryptDestroyHash(hHash);
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

Finally ending in this off-key crescendo:

    if (status == false)
    {
        delete hashBuffer;
        ::CryptDestroyHash(hHash);
        ::CryptReleaseContext(hCryptProvider, 0);
        return -1;
    }

I’m perfectly fine with the if statements being there. If one of those functions fail, then there’s nothing more to do and the code has to return. What I do have a problem with is the book keeping code that has to tag along at every step of the way, and that’s because it leads to two problems.

First one is maintainability. Repetition and maintainability don’t go well together.

The second and more important issue is this: What about errors I did not account for? What about this line for example:

BYTE* hashBuffer = new BYTE[hashBufferSize];

According to the standard, a call to new could fail by throwing a bad_alloc exception if memory can’t be allocated for one reason or another. Yes, I could surround it with a try-catch block and then repeat the clean up code yet again. But that’s not the point. The point is that exceptions can be thrown from seemingly innocent looking code, and accounting for all such cases is no trivial task.

One might argue that worrying about releasing resources after bad_alloc exceptions is like agonizing over filing the change of address forms at the local post office while the apocalypse is going down in T-minus five minutes.

Similarly one could argue that the chances of any of those cryptography functions failing is next to nil. How dangerous can generating a digest actually be?

I don’t know. And that’s the problem. I can reason, I can theorize, I can guestimate, but I simply don’t know the actual reliability of those functions, and I wouldn’t even know how to start testing it.

And that which you can’t test, you must guard against.

RIAA In Longhand

The best scenario would be to centralize the clean up code somewhere and still maintain robustness against both expected and unexpected errors. That’s where RIAA comes in.

Take for example the pair CryptAcquireContext and CrypeReleaseContext. If CryptAcquireContext is called successfully then its counterpart CryptReleaseContext must also be called at some point, regardless of what happens in the rest of the code.

In RIAA this is done by creating a class that has the sole purpose of calling the clean up code in its destructor. Here’s what such a class might look like in its simplest form:

class CrytpContextHandle{
public:
    CrytpContextHandle(HCRYPTPROV cryptContextHandle)
        : m_cryptContextHandle(cryptContextHandle)
    {
    }

    ~CrytpContextHandle()
    {
        if (m_cryptContextHandle == NULL)
            ::CryptReleaseContext(m_cryptContextHandle, 0);
    }

    HCRYPTPROV Handle() const
    {
        return m_cryptContextHandle;
    }

private:
    HCRYPTPROV m_cryptContextHandle;
};

And here’s how to use it:

    HCRYPTPROV hCryptProvider = NULL;
    HCRYPTHASH hHash = NULL;

    BOOL status = ::CryptAcquireContext
        (&hCryptProvider
        , NULL
        , NULL
        , PROV_RSA_FULL
        , 0
        );
    if (status == false)
        return -1;
    CrytpContextHandle cryptContextHandle(hCryptProvider);

Now CryptReleastContext will be called once the function terminates, whether it’s a normal termination or as a result of an unhandled exception.

Notice that the purpose of the class CrytpContextHandle isn’t to wrap the behavior of the Win32 Cryptography API in something more OO-friendly. It has a simpler and much narrower purpose, and the provided implementation satisfies that purpose succinctly.

A similar RAII class needs to be provided for CryptCreateHash and its counterpart CryptDestroyHash, and possibly another one to delete the hashBuffer. This is not a major burden, but these classes have limited utility since they’ll probably never be used elsewhere in the program.

Wouldn’t be nice if there was a simpler way yet?

Fortunately, there is.

RIAA in shorthand

Deleting the dynamically allocated hashBuffer can be made trivial by using boost::scoped_array. For the rest of the clean up code I’ll use a bit of magic called BOOST_SCOPE_EXIT. Here’s the revised implementation:

#include "GenerateSha1Hash.h"

#include <windows.h>
#include <algorithm>
#include <boost/scoped_array.hpp>
#include <boost/scope_exit.hpp>

int GenerateSha1Hash(const char* data, int8_t** hash, int hashSize)
{
    if (hashSize <= 0)
        return 0;

    HCRYPTPROV hCryptProvider = NULL;
    HCRYPTHASH hHash = NULL;

    BOOL status = ::CryptAcquireContext
        ( &hCryptProvider
        , NULL
        , NULL
        , PROV_RSA_FULL
        , 0
        );
    if (status == false)
        return -1;
    BOOST_SCOPE_EXIT((&hCryptProvider))
    {
        if (hCryptProvider != NULL)
            ::CryptReleaseContext(hCryptProvider, 0);
    } BOOST_SCOPE_EXIT_END

    status = ::CryptCreateHash(hCryptProvider, CALG_SHA1, 0, 0, &hHash);
    if (status == false)
        return -1;
    BOOST_SCOPE_EXIT((&hHash))
    {
        if (hHash != NULL)
            ::CryptDestroyHash(hHash);
    } BOOST_SCOPE_EXIT_END

    status = ::CryptHashData
        (hHash
        , reinterpret_cast<const BYTE*>(data)
        , strlen(data) + 1
        , 0
        );
    if (status == false)
        return -1;

    DWORD hashBufferSize = 0;
    DWORD hashBufferSizeSize = sizeof(DWORD)/sizeof(BYTE);

    status = ::CryptGetHashParam
        ( hHash
        , HP_HASHSIZE
        , reinterpret_cast<BYTE*>(&hashBufferSize)
        , &hashBufferSizeSize
        , 0
        );
     if (status == false)
        return -1;

    boost::scoped_array<BYTE> hashBuffer(new BYTE[hashBufferSize]);
    status = ::CryptGetHashParam
        ( hHash
        , HP_HASHVAL
        , hashBuffer.get()
        , &hashBufferSize
        , 0
        );
    if (status == false)
        return -1;

    int numberOfBytesToCopy = (static_cast<int>(hashBufferSize) < hashSize)?
                              hashBufferSize : hashSize;	

    std::copy(hashBuffer.get(), hashBuffer.get() + numberOfBytesToCopy, *hash);

    return numberOfBytesToCopy;}

This is much better and would have been even shorter if it wasn’t for my funky argument formatting style.

Here’s a closer look at one of the juicer bits:

    BOOL status = ::CryptAcquireContext
        ( &hCryptProvider
        , NULL
        , NULL
        , PROV_RSA_FULL
        , 0
        );
    if (status == false)
        return -1;
    BOOST_SCOPE_EXIT((&hCryptProvider))
    {
        if (hCryptProvider)
            ::CryptReleaseContext(hCryptProvider, 0);
    } BOOST_SCOPE_EXIT_END

Here a resource is acquired and the code that releases it is declared right underneath it inside the BOOST_SCOPE_EXIT macro, which will be called upon exit from the scope in which it resides.

The only hurdle is getting past the crusty ((&variableName)) syntax (which is explained in the Scope Exit tutorial). Otherwise, this is easier to maintain since the clean up code sits right next to the corresponding acquisition code and only has to be written once.

Summing Up

The RAII idiom rose out of necessity to compensate for missing features in C++ like automatic memory management. But I believe its usefulness extends well beyond that into areas like maintainability and fault tolerance. So much so that the RAII idiom is built into some languages that already have garbage collection, like C# with its using keyword and Python and its with statement.

No Comments

Powered by: