使用 CNG 创建哈希

哈希是对数据块执行的单向操作,用于创建表示数据内容的唯一哈希值。 无论何时执行哈希,对相同数据执行的相同 哈希算法 将始终生成相同的哈希值。 如果任何数据发生更改,哈希值将相应地更改。

哈希对加密数据没有用,因为它们不用于从哈希值重现原始数据。 哈希在与非对称签名算法一起使用时最适用于验证数据的完整性。 例如,如果对文本消息进行哈希处理,对哈希进行签名,并将已签名哈希值与原始消息一起包含,则接收方可以验证已签名哈希,为收到的消息创建哈希值,然后将此哈希值与原始消息附带的已签名哈希值进行比较。 如果两个哈希值相同,则收件人可以合理地确保原始邮件未被修改。

哈希值的大小对于特定哈希算法是固定的。 这意味着,无论数据块有多大或多小,哈希值都将始终是相同的大小。 例如,SHA256 哈希算法的哈希值大小为 256 位。

创建哈希对象

若要使用 CNG 创建哈希,请执行以下步骤:

  1. 打开支持所需算法的算法提供程序。 典型的哈希算法包括 MD2、MD4、MD5、SHA-1 和 SHA256。 调用 BCryptOpenAlgorithmProvider 函数并在 pszAlgId 参数中指定适当的算法标识符。 函数将句柄返回到提供程序。

  2. 执行以下步骤以创建哈希对象:

    1. 通过调用 BCryptGetProperty 函数检索 BCRYPT_OBJECT_LENGTH 属性来获取对象的大小。
    2. 分配内存以保存哈希对象。
    3. 通过调用 BCryptCreateHash 函数创建 对象。
  3. 对数据进行哈希处理。 这涉及到调用 BCryptHashData 函数一次或多次。 每个调用都会将指定的数据追加到哈希中。

  4. 执行以下步骤以获取哈希值:

    1. 通过调用 BCryptGetProperty 函数获取 BCRYPT_HASH_LENGTH 属性来检索值的大小。
    2. 分配内存以保存值。
    3. 通过调用 BCryptFinishHash 函数检索哈希值。 调用此函数后,哈希对象不再有效。
  5. 若要完成此过程,必须执行以下清理步骤:

    1. 通过将哈希句柄传递给 BCryptDestroyHash 函数来关闭哈希对象。

    2. 释放为哈希对象分配的内存。

    3. 如果不再创建哈希对象,请通过将提供程序句柄传递给 BCryptCloseAlgorithmProvider 函数来关闭算法提供程序。

      如果要创建更多哈希对象,建议重复使用算法提供程序,而不是多次创建和销毁同一类型的算法提供程序。

    4. 使用完哈希值内存后,请释放它。

以下示例演示如何使用 CNG 创建哈希值。

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) Microsoft. All rights reserved.
/*++

Abstract:

    Sample program for SHA 256 hashing using CNG

--*/


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



#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)

#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)


static const BYTE rgbMsg[] = 
{
    0x61, 0x62, 0x63
};


void __cdecl wmain(
                   int                      argc, 
                   __in_ecount(argc) LPWSTR *wargv)
{

    BCRYPT_ALG_HANDLE       hAlg            = NULL;
    BCRYPT_HASH_HANDLE      hHash           = NULL;
    NTSTATUS                status          = STATUS_UNSUCCESSFUL;
    DWORD                   cbData          = 0,
                            cbHash          = 0,
                            cbHashObject    = 0;
    PBYTE                   pbHashObject    = NULL;
    PBYTE                   pbHash          = NULL;

    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(wargv);

    //open an algorithm handle
    if(!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
                                                &hAlg,
                                                BCRYPT_SHA256_ALGORITHM,
                                                NULL,
                                                0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
        goto Cleanup;
    }

    //calculate the size of the buffer to hold the hash object
    if(!NT_SUCCESS(status = BCryptGetProperty(
                                        hAlg, 
                                        BCRYPT_OBJECT_LENGTH, 
                                        (PBYTE)&cbHashObject, 
                                        sizeof(DWORD), 
                                        &cbData, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    //allocate the hash object on the heap
    pbHashObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHashObject);
    if(NULL == pbHashObject)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

   //calculate the length of the hash
    if(!NT_SUCCESS(status = BCryptGetProperty(
                                        hAlg, 
                                        BCRYPT_HASH_LENGTH, 
                                        (PBYTE)&cbHash, 
                                        sizeof(DWORD), 
                                        &cbData, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    //allocate the hash buffer on the heap
    pbHash = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHash);
    if(NULL == pbHash)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    //create a hash
    if(!NT_SUCCESS(status = BCryptCreateHash(
                                        hAlg, 
                                        &hHash, 
                                        pbHashObject, 
                                        cbHashObject, 
                                        NULL, 
                                        0, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptCreateHash\n", status);
        goto Cleanup;
    }
    

    //hash some data
    if(!NT_SUCCESS(status = BCryptHashData(
                                        hHash,
                                        (PBYTE)rgbMsg,
                                        sizeof(rgbMsg),
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptHashData\n", status);
        goto Cleanup;
    }
    
    //close the hash
    if(!NT_SUCCESS(status = BCryptFinishHash(
                                        hHash, 
                                        pbHash, 
                                        cbHash, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptFinishHash\n", status);
        goto Cleanup;
    }

    wprintf(L"Success!\n");

Cleanup:

    if(hAlg)
    {
        BCryptCloseAlgorithmProvider(hAlg,0);
    }

    if (hHash)    
    {
        BCryptDestroyHash(hHash);
    }

    if(pbHashObject)
    {
        HeapFree(GetProcessHeap(), 0, pbHashObject);
    }

    if(pbHash)
    {
        HeapFree(GetProcessHeap(), 0, pbHash);
    }

}

创建可重用哈希对象

从Windows 8和Windows Server 2012开始,可以为需要快速连续计算多个哈希或 HMAC 的方案创建可重用哈希对象。 为此,请在调用 BCryptOpenAlgorithmProvider 函数时指定BCRYPT_HASH_REUSABLE_FLAG。 所有 Microsoft 哈希算法提供程序都支持此标志。 使用此标志创建的哈希对象可以在调用 BCryptFinishHash 后立即重复使用,就像它是通过调用 BCryptCreateHash 重新创建的一样。 执行以下步骤以创建可重用的哈希对象:

  1. 打开支持所需哈希算法的算法提供程序。 调用 BCryptOpenAlgorithmProvider 函数并在 pszAlgId 参数中指定适当的算法标识符,并在 dwFlags 参数中BCRYPT_HASH_REUSABLE_FLAG。 函数将句柄返回到提供程序。

  2. 执行以下步骤以创建哈希对象:

    1. 通过调用 BCryptGetProperty 函数检索 BCRYPT_OBJECT_LENGTH 属性来获取对象的大小。
    2. 分配内存以保存哈希对象。
    3. 通过调用 BCryptCreateHash 函数创建 对象。 在 dwFlags 参数中指定BCRYPT_HASH_REUSABLE_FLAG
  3. 通过调用 BCryptHashData 函数对数据进行哈希处理。

  4. 执行以下步骤以获取哈希值:

    1. 通过调用 BCryptGetProperty 函数获取 BCRYPT_HASH_LENGTH 属性来获取哈希值的大小。
    2. 分配内存以保存值。
    3. 通过调用 BCryptFinishHash 获取哈希值。
  5. 若要对新数据重复使用哈希对象,请转到步骤 3。

  6. 若要完成此过程,必须执行以下清理步骤:

    1. 通过将哈希句柄传递给 BCryptDestroyHash 函数来关闭哈希对象。
    2. 释放为哈希对象分配的内存。
    3. 如果不再创建哈希对象,请通过将提供程序句柄传递给 BCryptCloseAlgorithmProvider 函数来关闭算法提供程序。
    4. 使用完哈希值内存后,请释放它。

复制哈希对象

在某些情况下,对一定数量的常见数据进行哈希处理,然后根据公共数据创建两个单独的哈希对象可能很有用。 无需创建两个单独的哈希对象,对公共数据进行两次哈希处理就可完成此操作。 可以创建单个哈希对象,并将所有公共数据添加到哈希对象。 然后,可以使用 BCryptDuplicateHash 函数创建原始哈希对象的副本。 重复哈希对象包含与原始对象相同的所有状态信息和哈希数据,但它是完全独立的哈希对象。 现在可以向每个哈希对象添加唯一数据,并获取哈希值,如示例中所示。 对可能大量的常见数据进行哈希处理时,此方法非常有用。 只需将公共数据添加到原始哈希一次,然后就可以复制哈希对象来获取唯一的哈希对象。