HMAC SHA256 in C ++ (DynamoDB)
I am trying to connect to DynamoDB via a REST web interface and I need to generate a signature using HMAC-SHA256. I am working SHA-256 but I cannot get HMAC to work, 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 hmac call
/*********************************************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 am using:
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
With the same values, they lead to a different result. Can anyone help me why this is so? Thank.
source to share
The problem is that DynamoDB calculates hmac in two different ways. The first returns a string representation and the second returns a hexadecimal representation
Sixth realization
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());
}
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 a hexadecimal implementation for all date, region, service, and signature keys. String implementation is used for signature only
source to share
Mike has a bug. Don't use std :: string .length()
to find the length of a key when working with binary data. Because binary data can have a null character before the true end of the data. Or take array and length char
as parameters for key and msg. OR if you are using C ++ 11 you can use vector
to store 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);
}
source to share