VC++常用散列实现及Win32++结果展示

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), &paramSize, 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资源管理​​:

  • 使用CryptContextCryptHash内部结构体自动管理加密上下文和哈希对象
  • 构造函数获取资源,析构函数自动释放资源
  • 异常安全:即便发生异常也能正确释放资源

代码特点

  • 使用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::wstringstd::string相互转换

项目支持宽字符串,即win32xx::CStringGetString()函数返回的是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. 小结

  • CNGCryptoAPI更加fashion,更符合现代的需求,但需要考虑兼容性问题;
  • SHA-384SHA512等长度更长的算法,安全性会更好;
  • Win32++还是挺好用的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Humbunklung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值