Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HMAC SHA256 in C++ (DynamoDB)

I'm trying to connect to DynamoDB through the REST Web API and it requires me to generate a signature using HMAC-SHA256. I've got SHA-256 working, but I cant seem to get HMAC working, here is the C++ code (using OpenSSL)

string hmac(string key, string msg)
{
    unsigned char hash[32];

    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, &key[0], key.length(), EVP_sha256(), NULL);
    HMAC_Update(&hmac, (unsigned char*) &msg[0], msg.length());
    unsigned int len = 32;
    HMAC_Final(&hmac, hash, &len);
    HMAC_CTX_cleanup(&hmac);

    stringstream ss;
    for (int i = 0; i < len; i++)
    {   
        ss << hex  <<  ( unsigned int )hash[i];
    }

    return ss.str();
}

Here is the call to hmac

    /*********************************************CALCULATE SIGNATURE****************************************************************/

string AWS4 = "AWS4" + secretKey;

string Kdate = hmac(AWS4.data(), dateStamp);
string Kregion = hmac(Kdate.data(), region);
string Kservice = hmac(Kregion.data(), service);
string signingkey = hmac(Kservice.data(), "aws4_request");

string signature = hmac(signingkey.data(), stringToSign);

string authoritzationHeader = algorithm + " Credential=" + accessKey + "/" + credential_scope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;

This is the Python code I'm basing it off:

def sign(key, msg):
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

def getSignatureKey(key, date_stamp, regionName, serviceName):
    kDate    = sign(('AWS4' + key).encode('utf-8'), date_stamp)
    kRegion  = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, 'aws4_request')

    print 'Kdate: ' + kDate
    print 'Kregion: ' + kRegion 
    print 'Kservice: ' + kService

    return kSigning

Given the same values they produce a different result. Can anyone help me as to why this is? Thanks.

like image 888
Mike Howard Avatar asked Dec 01 '22 15:12

Mike Howard


2 Answers

The issue is that DynamoDB calculates hmac in two different ways. The first returns a string representation and the second returns a hex representation

The hex implementation

string hmacHex(string key, string msg)
{
    unsigned char hash[32];

    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, &key[0], key.length(), EVP_sha256(), NULL);
    HMAC_Update(&hmac, (unsigned char*)&msg[0], msg.length());
    unsigned int len = 32;
    HMAC_Final(&hmac, hash, &len);
    HMAC_CTX_cleanup(&hmac);

    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (int i = 0; i < len; i++)
    {   
        ss << std::hex << std::setw(2)  << (unsigned int)hash[i];
    }

    return (ss.str());
}

the string implementation

string hmac(string key, string msg)
{
    unsigned char hash[32];

    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, &key[0], key.length(), EVP_sha256(), NULL);
    HMAC_Update(&hmac, ( unsigned char* )&msg[0], msg.length());
    unsigned int len = 32;
    HMAC_Final(&hmac, hash, &len);
    HMAC_CTX_cleanup(&hmac);

    std::stringstream ss;
    ss << std::setfill('0');
    for (int i = 0; i < len; i++)
    {
        ss  << hash[i];
    }

    return (ss.str());
}

Amazon uses the hex implementation for all date, region, service and signing key. The string implementation is only used for the signature

like image 52
Mike Howard Avatar answered Dec 06 '22 19:12

Mike Howard


Mike's answer has a bug. Don't use std::strings .length() to find the length of the key when dealing with binary data. As binary data can have null character before the true end of the data. Either take in a char array and length as parameters for both key and msg. OR if you are using C++11, you can use vector to store the binary data.

Following is a partial implementation of Mike's answer with vectors as parameters-

std::vector<uint8_t> 
HMAC_SHA256(const std::vector<uint8_t>& key
           ,const std::vector<uint8_t>& value)
{
    unsigned int len = SHA256_DIGEST_LENGTH;
    unsigned char hash[SHA256_DIGEST_LENGTH];
    size_t keyLen = key.size();
    size_t valueLen = value.size();

    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, (unsigned char*)key.data(), keyLen, EVP_sha256(), NULL);
    HMAC_Update(&hmac, (unsigned char*)value.data(), valueLen);
    HMAC_Final(&hmac, hash, &len);
    HMAC_CTX_cleanup(&hmac);

    return std::vector<uint8_t>((uint8_t*)hash,(uint8_t*)hash+SHA256_DIGEST_LENGTH);
}
like image 39
Ashwin Avatar answered Dec 06 '22 20:12

Ashwin