如何用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; }
差异原因分析
- 密钥处理逻辑错误:微软示例代码是演示从密码派生RC4加密密钥,再用该密钥做HMAC,但PHP的
hash_hmac直接使用原始密钥计算HMAC,不需要经过哈希派生对称密钥的步骤。原代码中CryptDeriveKey会对密钥进行额外处理,导致实际用于HMAC的密钥和原始密码不一致。 - 密钥传递方式错误: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
相关产品推荐
相关产品推荐

