Search code examples
winapicryptoapihmacsha1

Wrong result for base64 string of HMAC_SHA1 using Crypto*** API


I'm trying to use the winapi crypto api to get a base64 encoded hash. I copied and modified to get the function below. I got it from here mostly - https://msdn.microsoft.com/en-us/library/windows/desktop/aa382379(v=vs.85).aspx

Running it with calcHmacSha1("message", "key") should give IIjfdNXyFGtIFGyvSWU3fp0L46Q=. However it gives me SlLDwKvAoGBJ0atki7QFfj/181k=, the non base64 version it gives is 4a 52 c3 c0 ab c0 a0 60 49 d1 ab 64 8b b4 05 7e 3f f5 f3 59. This is a same situation encountered here - CryptoAPI returns incorrect result for HMAC_SHA1 - however his solution does not work for key's greater then 16 characters, and that is my need.

#pragma comment (lib, "Crypt32.lib")
#include <wincrypt.h>

std::string calcHmacSha1(std::string msg, std::string key) {
    std::string hash;

    std::vector<BYTE> msgbytebuffer(msg.begin(), msg.end());
    std::vector<BYTE> keybytebuffer(key.begin(), key.end());

    // http://msdn.microsoft.com/en-us/library/Aa379863

    HCRYPTPROV  hProv       = NULL;
    HCRYPTHASH  hHash       = NULL;
    HCRYPTKEY   hKey        = NULL;
    HCRYPTHASH  hHmacHash   = NULL;
    PBYTE       pbHash      = NULL;
    DWORD       dwDataLen   = 0;
    BYTE*       Data1     = &keybytebuffer[0]; // {0x6b,0x65,0x79};
    BYTE*       Data2     = &msgbytebuffer[0]; // {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
    HMAC_INFO   HmacInfo;

    //--------------------------------------------------------------------
    // Zero the HMAC_INFO structure and use the SHA1 algorithm for
    // hashing.

    debug_log("sizeof(Data2)", sizeof(Data2));
    debug_log("sizeof(BYTE)", sizeof(BYTE));

    ZeroMemory(&HmacInfo, sizeof(HmacInfo));
    HmacInfo.HashAlgid = CALG_SHA1;

    //--------------------------------------------------------------------
    // Acquire a handle to the default RSA cryptographic service provider.

    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
       debug_log(" Error in AcquireContext 0x%08x", GetLastError());
       goto ErrorExit;
    }

    //--------------------------------------------------------------------
    // Derive a symmetric key from a hash object by performing the
    // following steps:
    //    1. Call CryptCreateHash to retrieve a handle to a hash object.
    //    2. Call CryptHashData to add a text string (password) to the
    //       hash object.
    //    3. Call CryptDeriveKey to create the symmetric key from the
    //       hashed password derived in step 2.
    // You will use the key later to create an HMAC hash object.

    if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
       debug_log("Error in CryptCreateHash 0x%08x \n", GetLastError());
       goto ErrorExit;
    }

    if (!CryptHashData(hHash, Data1, key.length() * sizeof(BYTE), 0)) {
       debug_log("Error in CryptHashData 1 0x%08x", GetLastError());
       goto ErrorExit;
    }

    if (!CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hKey)) {
       debug_log("Error in CryptDeriveKey 0x%08x", GetLastError());
       goto ErrorExit;
    }

    //--------------------------------------------------------------------
    // Create an HMAC by performing the following steps:
    //    1. Call CryptCreateHash to create a hash object and retrieve
    //       a handle to it.
    //    2. Call CryptSetHashParam to set the instance of the HMAC_INFO
    //       structure into the hash object.
    //    3. Call CryptHashData to compute a hash of the message.
    //    4. Call CryptGetHashParam to retrieve the size, in bytes, of
    //       the hash.
    //    5. Call malloc to allocate memory for the hash.
    //    6. Call CryptGetHashParam again to retrieve the HMAC hash.

    if (!CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmacHash)) {
       debug_log("Error in CryptCreateHash key 0x%08x", GetLastError());
       goto ErrorExit;
    }

    if (!CryptSetHashParam(hHmacHash, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0)) {
       debug_log("Error in CryptSetHashParam 0x%08x", GetLastError());
       goto ErrorExit;
    }

    if (!CryptHashData(hHmacHash, Data2, msg.length() * sizeof(BYTE), 0)) {
       debug_log("Error in CryptHashData 2 0x%08x", GetLastError());
       goto ErrorExit;
    }

    //--------------------------------------------------------------------
    // Call CryptGetHashParam twice. Call it the first time to retrieve
    // the size, in bytes, of the hash. Allocate memory. Then call
    // CryptGetHashParam again to retrieve the hash value.

    if (!CryptGetHashParam(hHmacHash, HP_HASHVAL, NULL, &dwDataLen, 0)) {
       debug_log("Error in CryptGetHashParam 0x%08x", GetLastError());
       goto ErrorExit;
    }

    pbHash = (BYTE*)malloc(dwDataLen);
    if(NULL == pbHash) {
       debug_log("unable to allocate memory\n");
       goto ErrorExit;
    }

    if (!CryptGetHashParam(hHmacHash, HP_HASHVAL, pbHash, &dwDataLen, 0)) {
       debug_log("Error in CryptGetHashParam 0x%08x", GetLastError());
       goto ErrorExit;
    }

    DWORD base64Size = 0;
    if (!CryptBinaryToString(pbHash, dwDataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &base64Size)) {
        debug_log("Error in CryptBinaryToString 1 0x%08x", GetLastError());
        goto ErrorExit;
    };

    WCHAR* base64 = new WCHAR[ base64Size + 1 ];
    if (!CryptBinaryToString(pbHash, dwDataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64, &base64Size)) {
        debug_log("Error in CryptBinaryToString 2 0x%08x", GetLastError());
        goto ErrorExit;
    };
    hash = string_cast<std::string>(base64);
    delete[] base64;
    debug_log("hash:", hash);

    // printf("The hash is:  ");
    // char chash[512];
    // for(DWORD i = 0 ; i < dwDataLen ; i++) {
    //    printf("%.2x ",pbHash[i]);
    // }
    MessageBox(NULL, L"hi", L"cap", 0);

    // Free resources.
    ErrorExit:
        if(hHmacHash) CryptDestroyHash(hHmacHash);
        if(hKey) CryptDestroyKey(hKey);
        if(hHash) CryptDestroyHash(hHash);
        if(hProv) CryptReleaseContext(hProv, 0);
        if(pbHash) free(pbHash);
        return hash;
}

Solution

  • the function work correct, not need fix it. but you not take to account what is key here used for HMAC - not direct your string key, but first, based on your string key, RC4 key created and HMAC calculated for this binary RC4 key.

    different keys -> different hash.

    windows not let direct use string keys. it algorithm is better - first convert weak string key to more strong binary key. however if you want use string key and got result for it - can use code like this:

    #define BLOCK_SIZE 64
    
    BOOL hmac(PCSTR key, PCSTR message, ALG_ID Algid)
    {
        UCHAR i_key_pad[BLOCK_SIZE], o_key_pad[BLOCK_SIZE];
    
        HCRYPTPROV  hProv;
        HCRYPTHASH  hHash;
        ULONG len = (ULONG)strlen(key), cb;
        BOOL f;
    
        if (f = CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
        {
            if (len > BLOCK_SIZE)
            {
                if (f = CryptCreateHash(hProv, Algid, 0, 0, &hHash))
                {
                    f = CryptHashData(hHash, (PBYTE)key, len, 0) &&
                        CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&len, &(cb = sizeof(len)), 0) &&
                        CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)(key = (PCSTR)alloca(len)), &len, 0);
    
                    CryptDestroyHash(hHash);
                }
            }
    
            if (f)
            {
                ULONG i = BLOCK_SIZE;
    
                do 
                {
                    UCHAR c = --i < len ? key[i] : 0;
    
                    i_key_pad[i] = 0x36 ^ c;
                    o_key_pad[i] = 0x5c ^ c;
    
                } while (i);
    
                if (f = CryptCreateHash(hProv, Algid, 0, 0, &hHash))
                {
                    f = CryptHashData(hHash, i_key_pad, sizeof(i_key_pad), 0) &&
                        CryptHashData(hHash, (PBYTE)message, (ULONG)strlen(message), 0) &&
                        CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&len, &(cb = sizeof(len)), 0) &&
                        CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)(key = (PCSTR)alloca(len)), &len, 0);
    
                    CryptDestroyHash(hHash);
    
                    if (f && (f = CryptCreateHash(hProv, Algid, 0, 0, &hHash)))
                    {
                        f = CryptHashData(hHash, o_key_pad, sizeof(o_key_pad), 0) &&
                            CryptHashData(hHash, (PBYTE)key, len, 0) &&
                            CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)key, &len, 0);
    
                        CryptDestroyHash(hHash);
    
                        if (f && len)
                        {
                            DbgPrint("\nThe hash is:  ");
                            do 
                            {
                                DbgPrint("%02x", (UCHAR)*key++);
                            } while (--len);
                            DbgPrint("\n");
                        }
                    }
                }
            }
    
            CryptReleaseContext(hProv, 0);
        }
    
        return f;
    }
    
    
    //The hash is:  2088df74d5f2146b48146caf4965377e9d0be3a4
    hmac("key","message", CALG_SHA1);