文章目录
1. 常用散列
MD5(Message Digest Algorithm 5)
哈希长度:128位(32位十六进制字符)。
特点:计算速度快,但存在严重的安全漏洞,如碰撞攻击(不同输入生成相同哈希)。
现状:已不推荐用于安全场景,但仍可用于非对抗性文件校验(如临时完整性检查)。
SHA-1(Secure Hash Algorithm 1)
哈希长度:160位(40位十六进制字符)。
特点:比MD5更安全,但2017年证实可被碰撞攻击破解。
现状:已逐步淘汰,部分旧系统仍可能使用,需尽快升级替代方案。
SHA256(SHA-2家族成员)
哈希长度:256位(64位十六进制字符)。
特点:目前安全可靠,结构复杂,抗碰撞性强,广泛用于高安全场景。
应用:SSL/TLS证书、区块链(如比特币)、数据完整性验证、数字签名等。
其中,SHA-2包括多种变体,比如SHA-224、SHA-256、SHA-384、SHA-512等,数字代表哈希值的位数。SHA256生成的哈希值是256位的,也就是64个十六进制字符。相比MD5和SHA-1,SHA256更安全,目前没有已知的有效攻击方法,所以被广泛应用于SSL/TLS证书、区块链(比如比特币)等需要高安全性的场合。
2. Cryptography API(CryptoAPI)
实现
2.1 代码实现
// CryptoHasher.h
#pragma once
#include <windows.h>
#include <wincrypt.h>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
class CryptoHasher {
public:
static std::string HashString(const std::string& input, ALG_ID algorithm) {
// 定义资源自动释放策略
struct CryptContext {
HCRYPTPROV hProv_ = 0;
CryptContext() {
if (!::CryptAcquireContext(&hProv_, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
throw std::runtime_error("CryptAcquireContext failed");
}
~CryptContext() { if (hProv_) ::CryptReleaseContext(hProv_, 0); }
};
struct CryptHash {
HCRYPTHASH hHash_ = 0;
CryptHash(HCRYPTPROV hProv, ALG_ID alg) {
if (!::CryptCreateHash(hProv, alg, 0, 0, &hHash_))
throw std::runtime_error("CryptCreateHash failed");
}
~CryptHash() { if (hHash_) ::CryptDestroyHash(hHash_); }
};
try {
// 自动管理加密上下文
CryptContext ctx;
// 创建哈希对象
CryptHash hasher(ctx.hProv_, algorithm);
// 计算哈希值
if (!::CryptHashData(hasher.hHash_,
reinterpret_cast<const BYTE*>(input.data()),
static_cast<DWORD>(input.size()), 0)) {
return "";
}
// 获取哈希长度
DWORD hashLen = 0;
DWORD paramSize = sizeof(hashLen);
if (!::CryptGetHashParam(hasher.hHash_, HP_HASHSIZE,
reinterpret_cast<BYTE*>(&hashLen), ¶mSize, 0)) {
return "";
}
// 获取哈希值
std::vector<BYTE> hashBytes(hashLen);
if (!::CryptGetHashParam(hasher.hHash_, HP_HASHVAL, hashBytes.data(), &hashLen, 0)) {
return "";
}
// 转换为十六进制字符串
std::string hexStr;
hexStr.reserve(hashLen * 2);
for (const auto& byte : hashBytes) {
char buf[3];
sprintf_s(buf, sizeof(buf), "%02x", byte);
hexStr.append(buf);
}
return hexStr;
}
catch (...) {
return ""; // 异常时返回空字符串
}
}
// 常用算法封装
static std::string MD5(const std::string& input) { return HashString(input, CALG_MD5); }
static std::string SHA1(const std::string& input) { return HashString(input, CALG_SHA1); }
static std::string SHA256(const std::string& input) { return HashString(input, CALG_SHA_256); }
};
2.2 代码说明
RAII资源管理:
- 使用
CryptContext
和CryptHash
内部结构体自动管理加密上下文和哈希对象 - 构造函数获取资源,析构函数自动释放资源
- 异常安全:即便发生异常也能正确释放资源
代码特点
- 使用
std::vector
替代原始指针管理哈希结果 - 预分配字符串内存
reserve()
提升转换效率 - 使用安全的
sprintf_s
替代传统sprintf
错误处理
- 关键操作使用异常机制
- 对外接口保持返回空字符串的兼容性
- 内部错误通过异常传递状态
使用方法举例
- (1)将此头文件加入项目
- (2)调用静态方法直接计算哈希:
#include "CryptoHasher.h"
void Demo() {
std::string data = "重要数据";
// 计算不同哈希值
auto file_md5 = CryptoHasher::MD5(data);
auto file_sha1 = CryptoHasher::SHA1(data);
auto file_sha256 = CryptoHasher::SHA256(data);
}
- (3)支持算法清单
CALG_MD2
CALG_MD5
CALG_SHA
CALG_SHA_256
CALG_SHA_384
CALG_SHA_512
注意:需要Windows XP SP3+支持SHA256算法。
3. CNG (Cryptography API: Next Generation)
实现
3.1 代码实现
// BcryptHasher.h
#pragma once
#include <windows.h>
#include <bcrypt.h>
#include <string>
#include <vector>
#include <stdexcept>
#pragma comment(lib, "Bcrypt.lib")
class BcryptHasher {
private:
// RAII 封装 BCRYPT_ALG_HANDLE
struct AlgorithmHandle {
BCRYPT_ALG_HANDLE handle = nullptr;
AlgorithmHandle(LPCWSTR algorithmId) {
NTSTATUS status = BCryptOpenAlgorithmProvider(
&handle, algorithmId, nullptr, 0);
if (!BCRYPT_SUCCESS(status)) {
throw std::runtime_error("BCryptOpenAlgorithmProvider failed");
}
}
~AlgorithmHandle() {
if (handle) {
BCryptCloseAlgorithmProvider(handle, 0);
}
}
};
// RAII 封装 BCRYPT_HASH_HANDLE
struct HashHandle {
BCRYPT_HASH_HANDLE handle = nullptr;
HashHandle(BCRYPT_ALG_HANDLE algHandle) {
NTSTATUS status = BCryptCreateHash(
algHandle, &handle, nullptr, 0, nullptr, 0, 0);
if (!BCRYPT_SUCCESS(status)) {
throw std::runtime_error("BCryptCreateHash failed");
}
}
~HashHandle() {
if (handle) {
BCryptDestroyHash(handle);
}
}
};
public:
static std::string HashData(const std::string& input, LPCWSTR algorithmId) {
try {
// 打开算法
AlgorithmHandle algHandle(algorithmId);
// 创建哈希对象
HashHandle hashHandle(algHandle.handle);
// 计算哈希
NTSTATUS status = BCryptHashData(
hashHandle.handle,
reinterpret_cast<PUCHAR>(const_cast<char*>(input.data())),
static_cast<ULONG>(input.size()),
0);
if (!BCRYPT_SUCCESS(status)) {
return "";
}
// 获取哈希长度
DWORD hashLength = 0;
DWORD resultSize = 0;
status = BCryptGetProperty(
algHandle.handle,
BCRYPT_HASH_LENGTH,
reinterpret_cast<PUCHAR>(&hashLength),
sizeof(hashLength),
&resultSize,
0);
if (!BCRYPT_SUCCESS(status)) {
return "";
}
// 获取哈希值
std::vector<UCHAR> hash(hashLength);
status = BCryptFinishHash(
hashHandle.handle, hash.data(), hashLength, 0);
if (!BCRYPT_SUCCESS(status)) {
return "";
}
// 转换为十六进制字符串
std::string hexStr;
hexStr.reserve(hashLength * 2);
for (const auto& byte : hash) {
char buf[3];
sprintf_s(buf, "%02x", byte);
hexStr += buf;
}
return hexStr;
}
catch (...) {
return "";
}
}
// 常用算法封装
static std::string MD5(const std::string& input) {
return HashData(input, BCRYPT_MD5_ALGORITHM);
}
static std::string SHA1(const std::string& input) {
return HashData(input, BCRYPT_SHA1_ALGORITHM);
}
static std::string SHA256(const std::string& input) {
return HashData(input, BCRYPT_SHA256_ALGORITHM);
}
};
3.2 代码说明
使用 CNG (Cryptography API: Next Generation):
- 替换旧的
CryptoAPI
,采用更现代的bcrypt.h
- 支持
Windows Vista
及以上系统 - 符合微软推荐的加密实践
资源管理优化:
AlgorithmHandle
管理算法句柄生命周期HashHandle
管理哈希对象生命周期- 异常安全保证资源释放
性能提升:
- 避免重复打开算法提供程序
- 使用
std::vector
预分配内存 - 减少内存拷贝操作
增强的错误处理: - 检查所有
NTSTATUS
返回值 - 异常与错误码结合处理
- 返回空字符串表示失败
使用示例
#include <iostream>
#include "BcryptHasher.h"
int main() {
std::string data = "Hello, World!";
std::cout << "MD5: " << BcryptHasher::MD5(data) << std::endl;
std::cout << "SHA-1: " << BcryptHasher::SHA1(data) << std::endl;
std::cout << "SHA256: " << BcryptHasher::SHA256(data) << std::endl;
return 0;
}
4. Win32++
桌面程序集成
4.1 std::wstring
和std::string
相互转换
项目支持宽字符串,即win32xx::CString
的GetString()
函数返回的是std::wstring
类型,这时候,而上例两种实现方法都是用了std::string
,因此需要对两种类型进行转换:
// stringconverter.h
#pragma once
#include <string>
#include <vector>
#include <stdexcept>
#ifdef _WIN32
#include <windows.h>
// std::string (UTF-8) → std::wstring (UTF-16)
std::wstring string_to_wstring(const std::string& str) {
if (str.empty()) return L"";
int size_needed = MultiByteToWideChar(
CP_UTF8, 0,
str.c_str(), (int)str.size(),
nullptr, 0
);
if (size_needed <= 0) {
throw std::runtime_error("MultiByteToWideChar failed");
}
std::vector<wchar_t> buffer(size_needed);
int result = MultiByteToWideChar(
CP_UTF8, 0,
str.c_str(), (int)str.size(),
buffer.data(), size_needed
);
if (result <= 0) {
throw std::runtime_error("MultiByteToWideChar failed");
}
return std::wstring(buffer.data(), buffer.size());
}
// std::wstring (UTF-16) → std::string (UTF-8)
std::string wstring_to_string(const std::wstring& wstr) {
if (wstr.empty()) return "";
int size_needed = WideCharToMultiByte(
CP_UTF8, 0,
wstr.c_str(), (int)wstr.size(),
nullptr, 0, nullptr, nullptr
);
if (size_needed <= 0) {
throw std::runtime_error("WideCharToMultiByte failed");
}
std::vector<char> buffer(size_needed);
int result = WideCharToMultiByte(
CP_UTF8, 0,
wstr.c_str(), (int)wstr.size(),
buffer.data(), size_needed,
nullptr, nullptr
);
if (result <= 0) {
throw std::runtime_error("WideCharToMultiByte failed");
}
return std::string(buffer.data(), buffer.size());
}
#else // Linux/macOS
#include <codecvt>
#include <locale>
// std::string (UTF-8) → std::wstring (UTF-32)
std::wstring string_to_wstring(const std::string& str) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.from_bytes(str);
}
// std::wstring (UTF-32) → std::string (UTF-8)
std::string wstring_to_string(const std::wstring& wstr) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.to_bytes(wstr);
}
#endif
4.2 代码集成
- 主要对话框构造
// digestdlg.h
#ifndef _DIGESTDLG_H__
#define __DIGESTDLG_H__
#include "stdafx.h"
#include "resource.h"
class CDigestDlg :
public CDialog
{
public:
CDigestDlg(UINT resID);
CDigestDlg(LPCWSTR resName);
virtual ~CDigestDlg() override = default;
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
virtual BOOL OnInitDialog();
virtual void OnDestroy();
protected:
virtual void DoDataExchange(CDataExchange& dx) override;
private:
CDigestDlg(const CDigestDlg&) = delete;
CDigestDlg& operator=(const CDigestDlg&) = delete;
CString m_strPlainText;
CString m_strMd5;
CString m_strSha1;
CString m_strSha256;
CEdit m_editMd5;
CEdit m_editSha1;
CEdit m_editSha256;
CButton m_btnCompute;
BOOL OnCompute();
// DDX/DDV
CDataExchange m_dx;
};
#endif // __DIGESTDLG_H__
// digestdlg.cpp
#include "DigestDlg.h"
//#include "cryptohasher.h"
#include "BcryptHasher.h"
#include "stringconverter.h"
CDigestDlg::CDigestDlg(UINT resID): CDialog(resID)
{
}
CDigestDlg::CDigestDlg(LPCWSTR resName): CDialog(resName)
{
}
BOOL CDigestDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
UINT id = LOWORD(wParam);
switch (id)
{
case IDC_BUTTON_COMPUTE: return OnCompute();
}
return FALSE;
}
BOOL CDigestDlg::OnInitDialog()
{
SetIconLarge(IDI_SMALL);
SetIconSmall(IDI_SMALL);
SetWindowText(LoadString(IDS_APP_TITLE));
SetDlgItemText(IDC_STATIC_PLAINTEXT, LoadString(IDS_PLAINTEXT));
SetDlgItemText(IDC_STATIC_MD5, LoadString(IDS_MD5));
SetDlgItemText(IDC_STATIC_SHA1, LoadString(IDS_SHA1));
SetDlgItemText(IDC_STATIC_SHA256, LoadString(IDS_SHA256));
//SetDlgItemText(IDC_BUTTON_COMPUTE, LoadString(IDS_COMPUTE));
AttachItem(IDC_BUTTON_COMPUTE, m_btnCompute);
AttachItem(IDC_EDIT_MD5, m_editMd5);
AttachItem(IDC_EDIT_SHA1, m_editSha1);
AttachItem(IDC_EDIT_SHA256, m_editSha256);
m_editMd5.SetReadOnly();
m_editSha1.SetReadOnly();
m_editSha256.SetReadOnly();
m_btnCompute.SetWindowTextW(LoadString(IDS_COMPUTE));
UpdateData(m_dx, SENDTOCONTROL);
return 0;
}
void CDigestDlg::OnDestroy()
{
::PostQuitMessage(0);
}
void CDigestDlg::DoDataExchange(CDataExchange& dx)
{
dx.DDX_Text(IDC_EDIT_PLAINTEXT, m_strPlainText);
dx.DDX_Text(IDC_EDIT_MD5, m_strMd5);
dx.DDX_Text(IDC_EDIT_SHA1, m_strSha1);
dx.DDX_Text(IDC_EDIT_SHA256, m_strSha256);
}
BOOL CDigestDlg::OnCompute()
{
UpdateData(m_dx, READFROMCONTROL);
std::string plainText = wstring_to_string(m_strPlainText.GetString());
m_strMd5 = BcryptHasher::MD5(plainText).c_str();
m_strSha1 = BcryptHasher::SHA1(plainText).c_str();
m_strSha256 = BcryptHasher::SHA256(plainText).c_str();
UpdateData(m_dx, SENDTOCONTROL);
return TRUE;
}
其中,OnCompute
函数是主要的调用示例。
- 代码执行效果
5. 小结
CNG
比CryptoAPI
更加fashion,更符合现代的需求,但需要考虑兼容性问题;SHA-384
、SHA512
等长度更长的算法,安全性会更好;Win32++
还是挺好用的。