Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple Way to Crypt a File with CNG

0.00/5 (No votes)
9 May 2007 1  
Cryptography API: The Next Generation (CNG) - How to crypt documents with C++ programming (without an understanding of cryptography or security)

Screenshot - CNGCryptFileSample1.gif

Contents

Introduction

This article explains some Cryptography API Next Generation (CNG) functions. This article is written under Windows Vista Business It's using Visual Studio C++ 2005 (Standard edition Sp1), Windows SDK and CNG SDK.

It's useful to:

  • save documents in a secure environment, especially over non-secure media such as the Internet
  • encrypt any kind of file, image, mp3 or document
  • create the product key of a software (with some adjustment)

Note: CNG is currently supported only on Windows Vista and it cannot be used with VB or C#. In order to build Windows applications with Visual Studio, you will need the Windows Vista SDK, which provides the documentation, samples, header files, libraries, and tools you need to develop applications that run on Windows. The CNG SDK is now available for download at the Microsoft Download Center.

Background

The basic idea is to encrypt using a CNG a file with a simple GUI. The 3 steps necessary are:

  • select Cryptographic Operations
  • select the original file and the file to encrypt
  • select the encryption key

The Application

I have created the project as an MFC application. The application has a single interface wherein you choose: the file you want to encrypt/decrypt, the operation methods (encrypt/decrypt) and the password. In the main dialog, you find a listbox; it catalogs the errors that occur as well as other useful information.

Set Up Visual Studio 2005

To use this class in Visual C++ 2005, you must add to the project properties thus:

  1. At C++ General in the "Additional Include Directories," add this path: C:\Program Files\Microsoft CNG Development Kit\Include.

    C++ General

  2. At "Linker General" in the "Additional Library Directories," add this path: C:\Program Files\Microsoft CNG Development Kit\Lib\X86.

    Linker General

  3. At "Linker Input," "Additional Dependencies", add bcrypt.lib.

    Linker Input

Using the Code

I created the class CMyCNGCryptFile that uses CNG. It has 3 public methods:

  • EnumProviders - enumerate the registered providers
  • CryptFile - encrypt or decrypt an input file
  • GetLastError - return the last error that occurred in CryptFile or EnumProviders

The Use of Cryptography

The steps involved in this application are:

  1. Open the algorithm provider
  2. Create or import a key
  3. Get and set algorithm properties
  4. Perform cryptographic operations
  5. Close the algorithm provider

The CNG API

To open the algorithm provider:

  • BCryptOpenAlgorithmProvider

To import a key:

  • BCryptGenerateSymmetricKey

To create a key:

  • BCryptCreateHash
  • BCryptHashData
  • BCryptFinishHash
  • BCryptGenerateSymmetricKey

To get the algorithm properties:

  • BCryptGetProperty

To set the algorithm properties:

  • BCryptSetProperty

To perform cryptographic operations:

  • BCryptEncrypt
  • BCryptDecrypt

To enumerate the providers:

  • BCryptEnumRegisteredProviders

To close the algorithm provider:

  • BCryptCloseAlgorithmProvider

To destroy the key:

  • BCryptDestroyKey

To destroy the hash:

  • BCryptDestroyHash

The Use of CMyCNGCryptFile

CryptFile

bool CryptFile(bool bEncrypt, CString sFileToOpen,CString sFileToCrypt,CString sKey)

This is the main method called from the Dlg. It takes the Operation to perform, the InputFile, the OutputFile and the CryptKey. The steps involved are:

  1. Open the algorithm provider with OpenMSPrimitiveProviderAES
  2. Create a key with CreateSymmetricKey_AES_CBC, or import a key with CreateSymmetricKey_SHA1_Hash
  3. Retrieve the buffer from file in
  4. Perform cryptographic operations with Crypt for the intermediate byte and CryptLastByte for the last byte
  5. Save crypt data to file out

OpenMSPrimitiveProviderAES

This method opens a handle to the AES provider.

bool CMyCNGCryptFile::OpenMSPrimitiveProviderAES()
{
    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
    ntStatus  = BCryptOpenAlgorithmProvider( &m_hAesAlg, 
        BCRYPT_AES_ALGORITHM, NULL, 0);
    switch (ntStatus)
    {
    case STATUS_SUCCESS:
        return true;
    case STATUS_INVALID_PARAMETER:
    case STATUS_NO_MEMORY:
    default:
    //... Check and log the error ...        
    }
    return false;
}

CreateSymmetricKey_AES_CBC

This method retrieves a key stored in the program as a static constant BYTE variable called rgbAES128Key. First, I get the property of the algorithm with BCryptGetProperty. Then, I use the algorithm provider handle to obtain implementation details for the algorithm, such as the key size and IV size. Next, I allocate it in the heap and modify the properties of the algorithm with BCryptSetProperty. Here, I want to use BCRYPT_CHAIN_MODE_CBC block cipher chaining with AES. I set the BCRYPT_CHAINING_MODE property of an AES algorithm to BCRYPT_CHAIN_MODE_CBC. Now, I create an ephemeral key with the BCryptGenerateSymmetricKey.

bool CMyCNGCryptFile::CreateSymmetricKey_AES_CBC(DWORD &cbKeyObject, 
    DWORD &cbIV )
{
    NTSTATUS    ntStatus = STATUS_UNSUCCESSFUL;
    DWORD        cbData    = 0; 

    cbKeyObject    = 0;
    cbIV  = 0;

    ntStatus = BCryptGetProperty(m_hAesAlg, BCRYPT_OBJECT_LENGTH, 
        (PBYTE)&cbKeyObject,  sizeof(DWORD), &cbData, 0);
    ...
    m_pbKeyObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbKeyObject);
    ...
    ntStatus = BCryptGetProperty( m_hAesAlg, BCRYPT_BLOCK_LENGTH, 
        (PBYTE)&cbIV, sizeof(DWORD), &cbData, 0);
    ...
    m_pbIV= (PBYTE) HeapAlloc (GetProcessHeap (), 0, cbIV);

    memcpy(m_pbIV, rgbIV, cbIV);

    ntStatus = BCryptSetProperty(m_hAesAlg, BCRYPT_CHAINING_MODE, 
        (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
    ... 
    ntStatus = BCryptGenerateSymmetricKey(m_hAesAlg, &m_hKey, m_pbKeyObject, 
        cbKeyObject, (PBYTE)rgbAES128Key, sizeof(rgbAES128Key), 0);
    ... 
    return true;
}

CreateSymmetricKey_SHA1_Hash

This method retrieves a key from the user. First, I open a new algorithm SHA1 with BCryptOpenAlgorithmProvider. I use SHA1 because the provider supports the hash interface used later. Then I get the property of the algorithm with BCryptGetProperty. I use the algorithm provider handle to obtain implementation details for the algorithm, such as the key size and hash size. After this, I allocate it in the heap and create the hash object for the key with BCryptCreateHash. Next, I use the BCryptHashData function to perform a one-way hash on a data buffer. I then retrieve the hash value for the data accumulated from prior calls to BCryptHashData. To do this, I use the BCryptFinishHash. Now I modify the properties of the algorithm with BCryptSetProperty. Here, I want to use BCRYPT_CHAIN_MODE_CBC block cipher chaining with AES. I set the BCRYPT_CHAINING_MODE property of an AES algorithm to BCRYPT_CHAIN_MODE_CBC. Finally, I create an ephemeral key with the BCryptGenerateSymmetricKey.

bool CMyCNGCryptFile::CreateSymmetricKey_SHA1_Hash(PCWSTR pwszText, 
    DWORD cbKeyObject)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BCRYPT_KEY_HANDLE    hKey = NULL;
    
    DWORD               cbHashObject, cbResult;
    BYTE                rgbHash[20];
    DWORD                cbData    = 0; 

    ntStatus = BCryptOpenAlgorithmProvider(&m_hHashAlg,
                     BCRYPT_SHA1_ALGORITHM,NULL,0);
    ... 
    ntStatus = BCryptGetProperty(m_hAesAlg, BCRYPT_OBJECT_LENGTH,
                    (PBYTE)&cbKeyObject,  sizeof(DWORD), &cbData, 0);
    ...    
    ntStatus = BCryptGetProperty( m_hHashAlg,BCRYPT_OBJECT_LENGTH,
                    (PBYTE) &cbHashObject,sizeof(DWORD),&cbResult,0);
    ...
    ntStatus = BCryptCreateHash(m_hHashAlg, &m_hHash, m_pbHashObject,
                    cbHashObject, NULL, 0, 0 );

    ntStatus = BCryptHashData( m_hHash,  (PBYTE)pwszText, (ULONG)wcslen(
        pwszText), 0);

    ntStatus = BCryptFinishHash( m_hHash, rgbHash, sizeof(rgbHash), 0);
    ...
    ntStatus = BCryptGenerateSymmetricKey( m_hAesAlg, &hKey,  m_pbKeyObject, 
                cbKeyObject, rgbHash, SYMM_KEY_SIZE_SECRET,  0  );
    ...
    return true;
}

Crypt

This method performs cryptographic operations, such as encrypting or decrypting data, by using the BCryptEncrypt or BCryptDecrypt functions. I use this method to encrypt data with the same length of ciphertext. If the data must encrypt, I use BCryptEncrypt, else I use BCryptEncrypt.

bool CMyCNGCryptFile::Crypt(bool bEncrypt,PUCHAR pbufFileToOpen, 
    ULONG iBytesRead, ULONG cbIV, PBYTE pbufFileToSave, DWORD& iBufToSave)
{    
    NTSTATUS ntStatus =STATUS_UNSUCCESSFUL;    
    DWORD        cbCipherText        = 0;

    if ( bEncrypt )                     
        ntStatus = BCryptEncrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL,
                   m_pbIV, cbIV, pbufFileToSave, iBytesRead, &iBufToSave, 0);

    else        
        ntStatus = BCryptDecrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL,
                   m_pbIV, cbIV, pbufFileToSave, iBytesRead, &iBufToSave,  0);

    ...  
    return false;
}

CryptLastByte

I use this method to encrypt data with different lengths of ciphertext. I call BCryptEncrypt or BCryptDecrypt twice. The first time, I obtain the size of the encrypted data. The second time, I retrieve the ciphertext.

bool CMyCNGCryptFile::CryptLastByte(bool bEncrypt,PUCHAR pbufFileToOpen, 
    ULONG iBytesRead, ULONG cbIV, PBYTE pbufFileToSave, DWORD& iBufToSave)
{
    NTSTATUS ntStatus= STATUS_UNSUCCESSFUL;
    DWORD        cbCipherText        = 0;

    if (bEncrypt)
    {
        ntStatus = BCryptEncrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL, 
                     m_pbIV, cbIV, NULL, 0, &cbCipherText, 
                     BCRYPT_BLOCK_PADDING);
        ...
        ntStatus = BCryptEncrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
                     m_pbIV, cbIV, pbufFileToSave, cbCipherText, 
                     &cbCipherText,BCRYPT_BLOCK_PADDING);
        iBufToSave        =    cbCipherText;
    ...    
    }
    else
    {
        ntStatus = BCryptDecrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
        m_pbIV, cbIV, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);
    ...        
        ntStatus = BCryptDecrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
        m_pbIV, cbIV, pbufFileToSave, cbCipherText, &cbCipherText, 
        BCRYPT_BLOCK_PADDING);
        ...
    }
    return false;
}

EnumProviders

This method retrieves the current providers installed on the machine. I call the function BCryptEnumRegisteredProviders to retrieve information about the registered providers. I enumerate the providers retrieved from pProviders, a PCRYPT_PROVIDERS structure.

bool CMyCNGCryptFile::EnumProviders(CStringList *lstRegisteredProviders)
{
    ...
    ntStatus = BCryptEnumRegisteredProviders(&cbBuffer, &pProviders);
    ...    
    for ( DWORD i = 0; i < pProviders->cProviders; i++)
    {
        sProvider.Format(_T("%s\n"), 
        pProviders->rgpszProviders[i]);
        lstRegisteredProviders->AddHead(sProvider);
    } 
    
    if (pProviders != NULL)
    {
        BCryptFreeBuffer(pProviders);
    }
    return true;
}

~CMyCNGCryptFile

The destructor closes the algorithm provider, deletes all of the pointers stored for memory leaks and destroys the key hash. I call the function BCryptCloseAlgorithmProvider to close the algorithm provider. I call the function BCryptDestroyKey to destroy the key and then I call the function BCryptDestroyHash destroy the hash. Finally, I enumerate the providers retrieved from pProviders, a PCRYPT_PROVIDERS structure:

CMyCNGCryptFile::~CMyCNGCryptFile()
{
    BCryptCloseAlgorithmProvider(m_hAesAlg,0);
    BCryptDestroyKey(m_hKey);
    HeapFree(GetProcessHeap(), 0, m_pbKeyObject);
    HeapFree(GetProcessHeap(), 0, m_pbIV);
    //Hash
    BCryptDestroyHash(m_hHash);
    free(m_pbHashObject);
    BCryptCloseAlgorithmProvider(m_hHashAlg,0);
}

Points of Interest

To deploy the application, I learned the MSDN Cryptography API: Next Generation. I think I'll be adding Signing Data, BLOB and the provider registration information soon. Stay tuned.

History

  • 6th May, 2007 - First release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here