CCNGHelper.h
#pragma once
#include <string>
#include <tchar.h>
#include <windows.h>
#include <bcrypt.h>#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif// 下一代加密辅助类
// 客户端: Windows Vista 及以上系统可用
// 服务器: Windows Server 2008 及以上系统可用
class CCNGHelper
{
public:CCNGHelper();~CCNGHelper();//// @brief: 初始化// @param: strAlgorithm 哈希算法名字符串, 可选类型如下:// 常见可选 "MD5", "SHA1", "SHA256", "SHA384", "SHA512"// // 全部支持可选如下:// "RSA", "RSA_SIGN", "DH", "DSA", "RC2", "RC4", "AES", "DES", "DESX", "3DES", "3DES_112", // "MD2", "MD4", "MD5", "SHA1", "SHA256", "SHA384", "SHA512",// "AES-GMAC", "AES-CMAC", "ECDSA_P256", "ECDSA_P384", "ECDSA_P521", "ECDH_P256", "ECDH_P384", "ECDH_P521", // "RNG", "FIPS186DSARNG", "DUAECRNG"// WINDOWS 8 及以上: "SP800_108_CTR_HMAC", "SP800_56A_CONCAT", "PBKDF2", "CAPI_KDF", "TLS1_1_KDF", "TLS1_2_KDF"// Windows 10 及以上: "ECDSA", "ECDH", "XTS-AES"// Windows 10 1803: "HKDF"// "CHACHA20_POLY1305"// // @ret: bool 操作是否成功bool Initialize(const _tstring& strAlgorithm = _T("MD5"));//// @brief: 反初始化// @ret: 无void Uninitialize();//// @brief: 重置// @ret: 无void Reset();//// @brief: 计算哈希值// @param: pData 数据缓冲// @param: ulSize 数据长度// @ret: bool 操作是否成功bool HashData(const void* pData, unsigned long long ulSize);//// @brief: 获取累积的哈希值结果// @param: bUpper 是否大写// @ret: _tstring 哈希值字符串_tstring FinishHash(bool bUpper = true);//// @brief: 获取文件的哈希值结果// @param: strPath 文件路径// @param: bUpper 是否大写// @ret: _tstring 哈希值结果字符串_tstring GetFileHash(const _tstring& strPath, bool bUpper = true);//// @brief: 获取数据的哈希值结果// @param: pData 数据指针// @param: ullSize 数据长度// @param: bUpper 是否大写// @ret: _tstring 哈希值结果字符串_tstring GetDataHash(const void* pData,unsigned long long ullSize,bool bUpper = true);private://// @brief: 计算哈希// @param: pData 数据指针// @param: ulSize 数据长度// @ret: 无bool _HashData(const void* pData, unsigned long ulSize);//// @brief: 字节内容转字符串// @param: pData 数据指针// @param: nSize 数据长度// @param: bUpper 是否大写// @ret: _tstring 转换后的字符串_tstring _BytesToString(const void* pData, size_t nSize, bool bUpper = true);//// @brief: 字符串转大小// @param: str 字符串// @ret: _tstring 转换后的字符串_tstring _ToUpper(const _tstring& str);//// @brief: 多字符字符串转宽字符串// @param: CodePage 代码页// @param: str 字符串// @ret: std::wstring 转换后的宽字符串std::wstring _MultiStrToWStr(UINT CodePage, const std::string& str);//// @brief: 字符串转宽字符串// @param: str 字符串// @ret: std::wstring 转换后的宽字符串std::wstring _TStrToWStr(const _tstring& str);private:BCRYPT_ALG_HANDLE m_hAlg = NULL; // 算法提供程序句柄BCRYPT_HASH_HANDLE m_hHash = NULL; // 哈希对象句柄PBYTE m_pbHashObject = NULL; // 哈希对象内存PBYTE m_pbHash = NULL; // 哈希值缓冲DWORD m_cbHash = 0; // 哈希值缓冲大小_tstring m_strAlgorithm; // 算法名std::string m_dataBuf; // 数据缓冲(用于文件读取)
};
CCNGHelper.cpp
#include "CCNGHelper.h"
#include <Wincrypt.h>#pragma comment(lib,"Bcrypt.lib")#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define FILE_HASH_BLOCK_SIZE (1024 * 1024 * 4)CCNGHelper::CCNGHelper():m_hAlg(NULL),m_hHash(NULL),m_pbHashObject(NULL),m_pbHash(NULL),m_cbHash(0)
{}CCNGHelper::~CCNGHelper()
{Uninitialize();
}bool CCNGHelper::Initialize(const _tstring& strAlgorithm/* = _T("MD5")*/
)
{bool bSuccess = false;NTSTATUS status = STATUS_UNSUCCESSFUL;DWORD cbData = 0;DWORD cbHashObject = 0;_tstring strAlgorithmName = _ToUpper(strAlgorithm);if (m_strAlgorithm == strAlgorithmName){return true;}Uninitialize();do{// 打开一个算法句柄// https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmproviderstatus = BCryptOpenAlgorithmProvider(&m_hAlg, //指向接收 CNG 提供程序句柄 的 BCRYPT_ALG_HANDLE 变量的指针strAlgorithmName.c_str(), //指向以 null 结尾的 Unicode 字符串的指针,该字符串标识请求的加密算法NULL, //指向以 null 结尾的 Unicode 字符串的指针,该字符串标识要加载的特定提供程序BCRYPT_HASH_REUSABLE_FLAG//修改函数行为的标志);if (!NT_SUCCESS(status)){break;}// 检索提供程序的子对象的大小(以字节为单位)// https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptgetpropertystatus = BCryptGetProperty(m_hAlg, //表示要获取其属性值的 CNG 对象的句柄BCRYPT_OBJECT_LENGTH, //指向以 null 结尾的 Unicode 字符串的指针,该字符串包含要检索的属性的名称(PBYTE)&cbHashObject, //接收属性值的缓冲区的地址sizeof(DWORD), //pbOutput 缓冲区的大小(以字节为单位)&cbData, //指向 ULONG 变量的指针,该变量接收复制到 pbOutput 缓冲区的字节数0 //一组标志,用于修改此函数的行为。 未为此函数定义任何标志);if (!NT_SUCCESS(status)){break;}// 在堆上分配哈希对象m_pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbHashObject);if (NULL == m_pbHashObject){break;}// 检索哈希提供程序的哈希值的大小(以字节为单位)// https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptgetpropertystatus = BCryptGetProperty(m_hAlg, //表示要获取其属性值的 CNG 对象的句柄BCRYPT_HASH_LENGTH, //指向以 null 结尾的 Unicode 字符串的指针,该字符串包含要检索的属性的名称(PBYTE)&m_cbHash, //接收属性值的缓冲区的地址sizeof(DWORD), //pbOutput 缓冲区的大小(以字节为单位)&cbData, //指向 ULONG 变量的指针,该变量接收复制到 pbOutput 缓冲区的字节数0 //一组标志,用于修改此函数的行为。 未为此函数定义任何标志);if (!NT_SUCCESS(status)){break;}// 在堆上分配哈希缓冲区m_pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_cbHash);if (NULL == m_pbHash){break;}// 创建哈希对象// https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptcreatehashstatus = ::BCryptCreateHash(m_hAlg, //创建提供程序时指定的算法必须支持哈希接口&m_hHash, //哈希或 MAC 对象的句柄m_pbHashObject, //接收哈希或 MAC 对象的缓冲区的指针cbHashObject, //pbHashObject 缓冲区的大小(以字节为单位)NULL, //指向缓冲区的指针0, //pbSecret 缓冲区的大小(以字节为单位)BCRYPT_HASH_REUSABLE_FLAG //修改函数行为的标志);if (!NT_SUCCESS(status)){break;}bSuccess = true;} while (false);if (bSuccess){m_strAlgorithm = strAlgorithmName;}else{Uninitialize();}return bSuccess;
}void CCNGHelper::Uninitialize()
{if (m_hAlg){::BCryptCloseAlgorithmProvider(m_hAlg, 0);m_hAlg = NULL;}if (m_hHash){::BCryptDestroyHash(m_hHash);m_hHash = NULL;}if (m_pbHashObject){::HeapFree(GetProcessHeap(), 0, m_pbHashObject);m_pbHashObject = NULL;}if (m_pbHash){::HeapFree(GetProcessHeap(), 0, m_pbHash);m_pbHash = NULL;}m_strAlgorithm.clear();
}void CCNGHelper::Reset()
{if (!m_strAlgorithm.empty()){// 检索从先前调用 BCryptHashData 累积的数据的哈希值(void)::BCryptFinishHash(m_hHash, //用于计算哈希或 MAC 的哈希或 MAC 对象的句柄m_pbHash, //指向接收哈希或 MAC 值的缓冲区的指针m_cbHash, //pbOutput 缓冲区的大小(以字节为单位)0 //一组标志,用于修改此函数的行为。 当前未定义任何标志,因此此参数应为零);}
}bool CCNGHelper::HashData(const void* lpData, unsigned long long ullSize
)
{const char* pDataBegin = (const char*)lpData;const unsigned long ulMaxBlockSize = UINT32_MAX;bool bSuccess = false;if (m_strAlgorithm.empty()){return false;}// 小于32位最大值则直接计算哈希值if (ullSize <= ulMaxBlockSize){return _HashData(pDataBegin, (unsigned long)ullSize);}// 分段计算哈希值while(ullSize > 0){unsigned long ulReadSize = (ullSize > ulMaxBlockSize) ? ulMaxBlockSize : (unsigned long)ullSize;if (!_HashData(pDataBegin, ulReadSize)){break;}pDataBegin += ulReadSize;ullSize -= ulReadSize;}return bSuccess;
}_tstring CCNGHelper::FinishHash(bool bUpper/* = true*/
)
{_tstring strResult;if (m_strAlgorithm.empty()){return strResult;}// 检索从先前调用 BCryptHashData 累积的数据的哈希值NTSTATUS status = ::BCryptFinishHash(m_hHash, //用于计算哈希或 MAC 的哈希或 MAC 对象的句柄m_pbHash, //指向接收哈希或 MAC 值的缓冲区的指针m_cbHash, //pbOutput 缓冲区的大小(以字节为单位)0 //一组标志,用于修改此函数的行为。 当前未定义任何标志,因此此参数应为零);if (NT_SUCCESS(status)){strResult = _BytesToString(m_pbHash, m_cbHash, bUpper);}return strResult;
}_tstring CCNGHelper::GetFileHash(const _tstring& strPath, bool bUpper/* = true*/
)
{HANDLE hFile = INVALID_HANDLE_VALUE;DWORD dwBlockSize = FILE_HASH_BLOCK_SIZE;DWORD dwBytesRead = 0;if (m_strAlgorithm.empty()){return _tstring(_T(""));}do{// 打开文件// https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-createfilewhFile = CreateFile(strPath.c_str(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);if (INVALID_HANDLE_VALUE == hFile){break;}if (m_dataBuf.empty()){m_dataBuf.resize(dwBlockSize);}// 读取文件数据// https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-readfilewhile (::ReadFile(hFile, &m_dataBuf[0], dwBlockSize, &dwBytesRead, NULL)){if (0 == dwBytesRead){break;}if (!_HashData(&m_dataBuf[0], dwBytesRead)){break;}}} while (false);if (INVALID_HANDLE_VALUE != hFile){::CloseHandle(hFile);}return FinishHash(bUpper);
}_tstring CCNGHelper::GetDataHash(const void* pData, unsigned long long ullSize,bool bUpper/* = true*/
)
{_tstring strResult;if (m_strAlgorithm.empty()){return strResult;}if (HashData(pData, ullSize)){strResult = FinishHash(bUpper);}return strResult;
}bool CCNGHelper::_HashData(const void* lpData, unsigned long ulSize
)
{// 数据缓冲区上执行单向哈希NTSTATUS status = ::BCryptHashData(m_hHash,(PUCHAR)lpData,ulSize,0);if (!NT_SUCCESS(status)){return false;}return true;
}_tstring CCNGHelper::_BytesToString(const void* lpData, size_t nSize,bool bUpper/* = true*/
)
{const TCHAR rgbDigitsUpper[] = _T("0123456789ABCDEF");const TCHAR rgbDigitsLower[] = _T("0123456789abcdef");_tstring strResult(nSize * 2, 0);LPCBYTE lpBytes = (LPCBYTE)lpData;if (bUpper){for (DWORD i = 0; i < nSize; i++){strResult[i * 2] = rgbDigitsUpper[lpBytes[i] >> 4];strResult[i * 2 + 1] = rgbDigitsUpper[lpBytes[i] & 0x0F];}}else{for (DWORD i = 0; i < nSize; i++){strResult[i * 2] = rgbDigitsLower[lpBytes[i] >> 4];strResult[i * 2 + 1] = rgbDigitsLower[lpBytes[i] & 0x0F];}}return strResult;
}_tstring CCNGHelper::_ToUpper(const _tstring& str
)
{_tstring strResult = str;for (auto& item : strResult){if (item >= _T('a') && item <= _T('z')){item -= 0x20;}}return strResult;
}std::wstring CCNGHelper::_MultiStrToWStr(UINT CodePage, const std::string& str
)
{//计算缓冲区所需的字符长度int cchWideChar = ::MultiByteToWideChar(CodePage, 0, str.c_str(), -1, NULL, NULL);std::wstring strResult(cchWideChar, 0);//成功则返回写入到指示的缓冲区的字符数size_t nConverted = ::MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size());//调整内容长度strResult.resize(nConverted);return strResult;
}std::wstring CCNGHelper::_TStrToWStr(const _tstring& str
)
{
#ifdef _UNICODEreturn str;
#elsereturn _MultiStrToWStr(CP_ACP, str);
#endif
}
main.cpp
#include <locale.h>
#include <tchar.h>
#include "Win32Utils/CTimeUtils.h"
#include "Win32Utils/CPathUtils.h"
#include "Win32Utils/CCNGHelper.h"int _tmain(int argc, LPCTSTR argv[])
{UNREFERENCED_PARAMETER(argc);UNREFERENCED_PARAMETER(argv);::setlocale(LC_ALL, "");unsigned long long ullStartTime;unsigned long long ullEndTime;CCNGHelper cngObj;cngObj.Initialize(_T("md5"));_tstring strCngHash;_tstring strCryptHash;//system("pause");int nCount = 1000;while (true){ullStartTime = CTimeUtils::GetCurrentTickCount();for (int i = 0; i < nCount; i++){strCngHash = cngObj.GetFileHash(CPathUtils::GetCurrentModulePath(), true);}ullEndTime = CTimeUtils::GetCurrentTickCount();_tprintf(_T("CCNGHelper MD5: %s Cost time: %lld ms\n"), strCngHash.c_str(), ullEndTime - ullStartTime);system("pause");}return 0;
}