C++中的HMAC SHA256(DynamoDB)

4

我正在尝试通过REST Web API连接到DynamoDB,它要求我使用HMAC-SHA256生成签名。我已经使SHA-256工作正常,但是我似乎无法使HMAC工作,以下是C++代码(使用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();
}

这里是调用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;

这是我参考的Python代码:

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

相同的值产生不同的结果。有人能帮我解释一下这是为什么吗?谢谢。


你能展示一下如何实际调用 hmac()吗?Python代码似乎在密钥字符串前面添加了“AWS4”。这对于它所做的事情似乎非常重要。 - WhozCraig
乍一看,这似乎是正确的。唯一不同的是编码方式,但这似乎不会改变它。大量的调试输出与十六进制字符转换看起来离未来并不遥远。 - WhozCraig
搞定了,其中一部分使用 hmac 计算并返回字符字符串,另一部分使用 hmac 计算并返回十六进制字符串。 - Mike Howard
我怀疑这是二进制结果还是ASCII十六进制字符串结果,但我认为你更熟悉用法。你应该发布一个答案,这样其他可能遇到类似问题的人也可以从中学习。 - WhozCraig
3个回答

15
问题在于DynamoDB使用两种不同的方式来计算hmac。第一种返回字符串表示,第二种返回十六进制表示。
十六进制实现
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 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());
}

亚马逊在所有日期、区域、服务和签名密钥上使用十六进制实现。字符串实现仅用于签名。


另一个需要注意的是 OpenSSL 的版本问题。可能会链接不同版本的 OpenSSL 进行编译,但在运行时会产生错误。 - Mike Howard

3

迈克的回答有一个bug。在处理二进制数据时,不要使用std::strings.length()来查找键的长度。因为二进制数据可以在真正结束之前有空字符。要么输入char数组和键和msg的长度作为参数。或者,如果您使用的是C++11,可以使用vector来存储二进制数据。

以下是使用向量作为参数的Mike答案的部分实现-

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);
}

3
指出的错误并不存在。std::string::length() 返回字符串的字节数,无论其中是否包含零字节。以下代码可以证明这一点:std::string s("\0Hello!", 7); std::cout << s << std::endl << "s.length() is " << s.length() << std::endl; - Sasha Gervais-Tourangeau

0
static int C_hmac(){
    /**
    *   man 3 HMAC
    * */
    std::string data ="your's data" ;
    std::string key = "your's key";
    unsigned int hash_sz = EVP_MAX_MD_SIZE;
    HMAC_CTX ctx;
    HMAC_CTX_init(&ctx);
    unsigned char* digest = HMAC(EVP_sha256(), key.c_str(), key.size(), (unsigned char*)data.c_str(), data.size(), NULL, &hash_sz);

    std::stringstream ss;
    ss<< std::setfill('0');
    for(int i=0;i< hash_sz ;++i){
       ss << std::hex << std::setw(2) << (unsigned int) digest[i];
    }
    std::string final_hash = ss.str();
    return 1;
}

1
来自评论:嗨,请不要只回答源代码。请尝试提供有关您的解决方案如何工作的良好描述。请参阅:https://stackoverflow.com/help/how-to-answer。谢谢。 - Taazar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接