다음을 통해 공유


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 해시 알고리즘 공급자는 이 플래그를 지원합니다. 이 플래그를 사용하여 만든 해시 개체는 BCryptCreateHash를 호출하여 새로 만든 것처럼 BCryptFinishHash를 호출한 직후에 다시 사용할 수 있습니다. 재사용 가능한 해시 개체를 만들려면 다음 단계를 수행합니다.

  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 함수를 사용하여 원래 해시 개체의 복제본을 만들 수 있습니다. 중복 해시 개체는 원본과 동일한 상태 정보와 해시된 데이터를 모두 포함하지만 완전히 독립적인 해시 개체입니다. 이제 각 해시 개체에 고유 데이터를 추가하고 예제와 같이 해시 값을 가져올 수 있습니다. 이 기술은 많은 양의 공통 데이터를 해시할 때 유용합니다. 원래 해시에 공통 데이터를 한 번만 추가하면 됩니다. 그런 다음 해시 개체를 복제하여 고유한 해시 개체를 가져올 수 있습니다.