You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用WinCrypt在C++中实现PHP的hash_hmac函数?

WinCrypt实现HMAC与PHP hash_hmac结果不匹配问题排查与修复

问题描述

尝试用WinCrypt在C++中实现PHP的hash_hmac函数,参考微软官方《Example C Program: Creating an HMAC》示例代码,运行后得到哈希值:48 f2 57 38 29 29 43 16 fd f4 db 58 31 e1 0c 74 48 8e d4 e2。

测试数据为密钥"password"、消息"message",用PHP的hash_hmac两种参数顺序调用:

echo(hash_hmac('sha1', "password", "message", false));
echo PHP_EOL;
echo(hash_hmac('sha1', "message", "password", false));

得到结果:

f5959d1d5c133fd9368218e1ffde3075382d7fd7
4c2de361ba8958558de3d049ed1fb5c115656e65

C++结果与PHP完全不匹配,需要找出差异并修复(禁止使用OpenSSL)。

原C++代码:

#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

int main()
{
//--------------------------------------------------------------------
// Declare variables.
//
// hProv:           Handle to a cryptographic service provider (CSP). 
//                  This example retrieves the default provider for  
//                  the PROV_RSA_FULL provider type.  
// hHash:           Handle to the hash object needed to create a hash.
// hKey:            Handle to a symmetric key. This example creates a 
//                  key for the RC4 algorithm.
// hHmacHash:       Handle to an HMAC hash.
// pbHash:          Pointer to the hash.
// dwDataLen:       Length, in bytes, of the hash.
// Data1:           Password string used to create a symmetric key.
// Data2:           Message string to be hashed.
// HmacInfo:        Instance of an HMAC_INFO structure that contains 
//                  information about the HMAC hash.
// 
HCRYPTPROV  hProv       = NULL;
HCRYPTHASH  hHash       = NULL;
HCRYPTKEY   hKey        = NULL;
HCRYPTHASH  hHmacHash   = NULL;
PBYTE       pbHash      = NULL;
DWORD       dwDataLen   = 0;
BYTE        Data1[]     = {0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64};
BYTE        Data2[]     = {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
HMAC_INFO   HmacInfo;

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

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

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

if (!CryptAcquireContext(
    &hProv,                   // handle of the CSP
    NULL,                     // key container name
    NULL,                     // CSP name
    PROV_RSA_FULL,            // provider type
    CRYPT_VERIFYCONTEXT))     // no key access is requested
{
   printf(" Error in AcquireContext 0x%08x \n",
          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,                    // handle of the CSP
    CALG_SHA1,                // hash algorithm to use
    0,                        // hash key
    0,                        // reserved
    &hHash))                  // address of hash object handle
{
   printf("Error in CryptCreateHash 0x%08x \n",
          GetLastError());
   goto ErrorExit;
}

if (!CryptHashData(
    hHash,                    // handle of the hash object
    Data1,                    // password to hash
    sizeof(Data1),            // number of bytes of data to add
    0))                       // flags
{
   printf("Error in CryptHashData 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptDeriveKey(
    hProv,                    // handle of the CSP
    CALG_RC4,                 // algorithm ID
    hHash,                    // handle to the hash object
    0,                        // flags
    &hKey))                   // address of the key handle
{
   printf("Error in CryptDeriveKey 0x%08x \n", 
          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,                    // handle of the CSP.
    CALG_HMAC,                // HMAC hash algorithm ID
    hKey,                     // key for the hash (see above)
    0,                        // reserved
    &hHmacHash))              // address of the hash handle
{
   printf("Error in CryptCreateHash 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptSetHashParam(
    hHmacHash,                // handle of the HMAC hash object
    HP_HMAC_INFO,             // setting an HMAC_INFO object
    (BYTE*)&HmacInfo,         // the HMAC_INFO object
    0))                       // reserved
{
   printf("Error in CryptSetHashParam 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

if (!CryptHashData(
    hHmacHash,                // handle of the HMAC hash object
    Data2,                    // message to hash
    sizeof(Data2),            // number of bytes of data to add
    0))                       // flags
{
   printf("Error in CryptHashData 0x%08x \n", 
          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,                // handle of the HMAC hash object
    HP_HASHVAL,               // query on the hash value
    NULL,                     // filled on second call
    &dwDataLen,               // length, in bytes, of the hash
    0))
{
   printf("Error in CryptGetHashParam 0x%08x \n", 
          GetLastError());
   goto ErrorExit;
}

pbHash = (BYTE*)malloc(dwDataLen);
if(NULL == pbHash) 
{
   printf("unable to allocate memory\n");
   goto ErrorExit;
}
    
if (!CryptGetHashParam(
    hHmacHash,                 // handle of the HMAC hash object
    HP_HASHVAL,                // query on the hash value
    pbHash,                    // pointer to the HMAC hash value
    &dwDataLen,                // length, in bytes, of the hash
    0))
{
   printf("Error in CryptGetHashParam 0x%08x \n", GetLastError());
   goto ErrorExit;
}

// Print the hash to the console.

printf("The hash is:  ");
for(DWORD i = 0 ; i < dwDataLen ; i++) 
{
   printf("%2.2x ",pbHash[i]);
}
printf("\n");

// 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 0;
}

差异原因分析

  1. 密钥处理逻辑错误:微软示例代码是演示从密码派生RC4加密密钥,再用该密钥做HMAC,但PHP的hash_hmac直接使用原始密钥计算HMAC,不需要经过哈希派生对称密钥的步骤。原代码中CryptDeriveKey会对密钥进行额外处理,导致实际用于HMAC的密钥和原始密码不一致。
  2. 密钥传递方式错误:WinCrypt的CALG_HMAC支持直接导入原始密钥,无需派生加密密钥。原代码用派生的RC4密钥创建HMAC哈希对象,完全偏离了PHP的逻辑。

修复后的代码

移除多余的密钥派生步骤,直接导入原始密钥用于HMAC计算:

#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

int main()
{
    HCRYPTPROV  hProv       = NULL;
    HCRYPTKEY   hKey        = NULL;
    HCRYPTHASH  hHmacHash   = NULL;
    PBYTE       pbHash      = NULL;
    DWORD       dwDataLen   = 0;
    // 密钥:"password"
    BYTE        key[]       = {0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64};
    // 消息:"message"
    BYTE        message[]   = {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
    HMAC_INFO   HmacInfo;

    // 初始化HMAC_INFO,指定SHA1算法
    ZeroMemory(&HmacInfo, sizeof(HmacInfo));
    HmacInfo.HashAlgid = CALG_SHA1;

    // 获取CSP上下文
    if (!CryptAcquireContext(
        &hProv,
        NULL,
        NULL,
        PROV_RSA_FULL,
        CRYPT_VERIFYCONTEXT))
    {
        printf("Error in CryptAcquireContext 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 直接导入原始密钥用于HMAC
    // 构造简单的密钥Blob(适用于原始字节密钥)
    struct {
        BLOBHEADER hdr;
        DWORD      keySize;
        BYTE       keyData[sizeof(key)];
    } keyBlob;

    ZeroMemory(&keyBlob, sizeof(keyBlob));
    keyBlob.hdr.bType = PLAINTEXTKEYBLOB;
    keyBlob.hdr.bVersion = CUR_BLOB_VERSION;
    keyBlob.hdr.reserved = 0;
    keyBlob.hdr.aiKeyAlg = CALG_HMAC;
    keyBlob.keySize = sizeof(key);
    memcpy(keyBlob.keyData, key, sizeof(key));

    if (!CryptImportKey(
        hProv,
        (BYTE*)&keyBlob,
        sizeof(keyBlob),
        0,
        0,
        &hKey))
    {
        printf("Error in CryptImportKey 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 创建HMAC哈希对象
    if (!CryptCreateHash(
        hProv,
        CALG_HMAC,
        hKey,
        0,
        &hHmacHash))
    {
        printf("Error in CryptCreateHash 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 设置HMAC算法信息
    if (!CryptSetHashParam(
        hHmacHash,
        HP_HMAC_INFO,
        (BYTE*)&HmacInfo,
        0))
    {
        printf("Error in CryptSetHashParam 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 哈希消息数据
    if (!CryptHashData(
        hHmacHash,
        message,
        sizeof(message),
        0))
    {
        printf("Error in CryptHashData 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 获取哈希长度并分配内存
    if (!CryptGetHashParam(
        hHmacHash,
        HP_HASHVAL,
        NULL,
        &dwDataLen,
        0))
    {
        printf("Error in CryptGetHashParam 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

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

    // 获取哈希值
    if (!CryptGetHashParam(
        hHmacHash,
        HP_HASHVAL,
        pbHash,
        &dwDataLen,
        0))
    {
        printf("Error in CryptGetHashParam 0x%08x \n", GetLastError());
        goto ErrorExit;
    }

    // 输出哈希值(转为小写十六进制,和PHP格式一致)
    printf("The hash is: ");
    for (DWORD i = 0; i < dwDataLen; i++)
    {
        printf("%02x", pbHash[i]);
    }
    printf("\n");

ErrorExit:
    if (hHmacHash) CryptDestroyHash(hHmacHash);
    if (hKey) CryptDestroyKey(hKey);
    if (hProv) CryptReleaseContext(hProv, 0);
    if (pbHash) free(pbHash);
    return 0;
}

验证结果

修复后的代码运行后,输出的哈希值为4c2de361ba8958558de3d049ed1fb5c115656e65,与PHP中hash_hmac('sha1', "message", "password", false)的结果完全一致。

如果需要匹配hash_hmac('sha1', "password", "message", false),只需交换代码中key和message的内容即可。

内容的提问来源于stack exchange,提问作者Andrew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:48:11