Encrypting and Decrypting Data

An encryption key is needed before invoking encryption and decryption operations. This key is obtained by using the CryptGenKey, CryptDeriveKey, or CryptImportKey functions. The encryption algorithm is specified when the key is created. You can also specify additional encryption parameters, using the CryptSetKeyParam function.

For more information on CryptGenKey and CryptDeriveKey, see Generating Cryptographic Keys. For more information on CryptImportKey, see Exchanging Cryptographic Keys.

The following table shows the functions you can use to encode and decode a message.

Function
Description
CryptEncrypt Encodes a section of plaintext, using the specified encryption key
CryptDecrypt Decodes a section of cipher, using the specified decryption key

To enable the user to decode the data in the future, the CryptExportKey function is used to save the decryption key in a key BLOB that can only be decoded with the user's private key. This function requires the user's key exchange public key for this purpose, which can be obtained by using the CryptGetUserKey function. CryptExportKey returns a key BLOB that must be stored by the application for use in decoding the file.

To encode a file so that only the current user can access its data, bulk encode the file with a symmetric cipher. The key to this cipher is kept in the key BLOB that can only be decoded with the user's private key. This technique also works for encoding messages for specific recipients.

To encode a message, a session key must first be generated by using the CryptGenKey function. Calling this function generates a random key and returns a handle so that the key can encode and decode data. You should specify the encryption algorithm at this point. Because CAPI does not permit applications to use public-key algorithms to encode bulk data, call CryptGenKey to specify a symmetric algorithm, such as RC2 or RC4, for your application.

Alternatively, if your application needs to encode the message in such a way that anyone with a specified password can decode the data, the CryptDeriveKey function should be used to transform the password into a key suitable for encryption. In this case, CryptDeriveKey is called instead of CryptGenKey, and the subsequent CryptExportKey calls are not needed.

Once the key is generated, other cryptographic properties of the key can be set with CryptSetKeyParam. For example, different sections of the file can be encoded with different salt values, and the cipher mode or initialization vector can be changed. Applications can generate salt values with the CryptGenRandom function.

In block ciphers, you can change the method of encryption by setting the block cipher properties with CryptSetKeyParam. The following table shows the ciper modes.

Cipher mode
Description
Electronic codebook (ECB) Encodes blocks individually. No feedback is used.
Cipher block chaining (CBC) Encodes blocks, using feedback to ensure uniqueness
Cipher feedback mode (CFB) Encodes small increments of plaintext at a time, not entire blocks
Output feedback mode (OFB) Encodes similarly to CFB, but uses a different method for filling shift registers

Electronic codebook (ECB): In this cipher mode, each block is encoded individually and no feedback is used. This means that identical blocks of plaintext encoded with the same key are transformed into identical cipher blocks. If a single bit of the cipher block is garbled, then the entire corresponding plaintext block is also garbled.

Cipher block chaining (CBC): Each plaintext block in this cipher mode is encoded based on the cipher of the previous block. CBC ensures that even if the plaintext contains many identical blocks, each encodes to a different cipher block. Similarly to EBC, if a single bit of the cipher block is garbled, the corresponding plaintext block is also garbled. Moreover, a bit in the subsequent plaintext block in the same position as the original garbled bit, is garbled. If there are extra or missing bytes in the cipher, the plaintext is garbled from that point on.

Cipher feedback mode (CFB): In this cipher mode, small increments of plaintext can be processed into cipher, instead of processing entire blocks at a time. CFB is useful in some situations. For example, data originating from a keyboard can be encoded at each keystroke without waiting for an entire block to be typed.

This mode uses a shift register that is one block size in length and divided up into sections. For example, if the block size is 64 bits with 8 bits processed at a time, then the shift register is divided into eight sections.

CFB follows this process for each encryption cycle:

  1. The shift register is filled with the initialization vector.
  2. The block in the shift register is encoded.
  3. The leftmost 8 bits in the encoded shift register are matched with the next 8 bits of plaintext and sent off as 8 bits of cipher.
  4. The shift register shifts 8 bits to the left.
  5. The 8 bits of cipher generated in step 2 are placed in the rightmost 8 bits of the shift register.

In CAPI, the number of bits processed at a time is specified by setting the encryption key's KP_MODE_BITS parameter using the CryptSetKeyParam function. The default value for this parameter is typically 8 bits.

If 1 bit in the cipher is garbled, 1 bit in the plaintext is garbled, and the shift register is corrupted. This corruption results in the corrupting of subsequent plaintext blocks until the bad bit is shifted out of the shift register.

Output feedback mode (OFB): This cipher mode is identical to CFB, except the shift register is filled differently. If 1 bit in the cipher is garbled, the corresponding bit of plaintext is also garbled. If there are extra or missing bits from the cipher, the plaintext is garbled from that point on.

If the application does not explicitly specify one of these modes, then CBC is used.

Encode the data in the file with the CryptEncrypt function, which takes the previously generated session key and encodes a buffer of data. As the data is encoded, it may be slightly expanded by the encryption algorithm. The application is responsible for remembering the length of the encoded data so the proper length can later be given to the CryptDecrypt function.

If your application has certificates or public keys for other users, it can permit other users to decode the file by performing CryptExportKey calls for each user to whom it wants to give access. The returned key BLOBs must be stored by the application, as in the previous paragraph.

Once a file or message has been encoded, the following data must be stored by the application:

All parameters that were specified with the CryptSetKeyParam function as the message was being encoded must also be specified as the message is decoded. It may be appropriate to store some of these parameters with the encoded message, as well.

The following code example reads data from a text file named Test2.txt, encodes it using the RC2 block cipher, and writes out the encoded data to a file named Test.xxx. A random session key is generated to perform the encryption and is stored to the output file along with the encoded data. This session key is encoded with the user's key exchange public key by the CryptExportKey function.

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

#define BLOCK_SIZE            1000
#define BUFFER_SIZE           1008

BOOL EncryptFile (LPTSTR, LPTSTR, LPTSTR);

/***********************************************************************

  WinMain

***********************************************************************/
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    LPTSTR lpCmdLine, int nCmdShow)            
{
  LPTSTR lpszSource  = TEXT("test2.txt");
  LPTSTR lpszDestination = TEXT("test.xxx");
  LPTSTR lpszPassword  = TEXT("password");
  
  if (!EncryptFile (lpszSource, lpszDestination, lpszPassword)) 
  {
    wprintf (TEXT("Error encrypting file!\n"));
    return 1;
  }

  return 0;
}

/***********************************************************************

  EncryptFile

***********************************************************************/
BOOL EncryptFile (LPTSTR lpszSource, LPTSTR lpszDestination, 
                  LPTSTR lpszPassword)
{
  FILE *hSrcFile = NULL, 
       *hDestFile = NULL;

  HCRYPTPROV hProv = 0;
  HCRYPTHASH hHash = 0;
  HCRYPTKEY hKey = 0, 
            hXchgKey = 0;

  PBYTE pbBuffer = NULL, 
        pbKeyBlob = NULL;

  BOOL bEOF = 0, 
       bReturn = FALSE;

  DWORD dwCount, 
        dwKeyBlobLen;

  // Open the source file.
  if ((hSrcFile = _wfopen (lpszSource, TEXT("rb"))) == NULL) 
  {
    wprintf (TEXT("Error opening Plaintext file!\n"));
    goto exit;
  }

  // Open the destination file.
  if ((hDestFile = _wfopen (lpszDestination, TEXT("wb"))) == NULL) 
  {
    wprintf (TEXT("Error opening Ciphertext file!\n"));
    goto exit;
  }

  // Get the handle to the default provider.
  if (!CryptAcquireContext (&hProv, NULL, NULL, PROV_RSA_FULL, 0)) 
  {
    wprintf (TEXT("Error %x during CryptAcquireContext!\n"), 
             GetLastError ());
    goto exit;
  }

  if (lpszPassword == NULL) 
  {
    // Encrypt the file with a random session key.

    // Create a random session key.
    if (!CryptGenKey (hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) 
    {
      wprintf (TEXT("Error %x during CryptGenKey!\n"), 
               GetLastError ());
      goto exit;
    }

    // Get the handle to the key exchange public key.
    if (!CryptGetUserKey (hProv, AT_KEYEXCHANGE, &hXchgKey)) 
    {
      wprintf (TEXT("Error %x during CryptGetUserKey!\n"), 
               GetLastError ());
      goto exit;
    }

    // Determine the size of the key BLOB and allocate memory.
    if (!CryptExportKey (hKey, hXchgKey, SIMPLEBLOB, 0, NULL, 
                         &dwKeyBlobLen)) 
    {
      wprintf (TEXT("Error %x computing blob length!\n"), 
               GetLastError ());
      goto exit;
    }

    if ((pbKeyBlob = malloc (dwKeyBlobLen)) == NULL) 
    {
      wprintf (TEXT("Out of memory!\n"));
      goto exit;
    }

    // Export the session key into a simple key BLOB.
    if (!CryptExportKey (hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, 
                         &dwKeyBlobLen)) 
    {
      wprintf (TEXT("Error %x during CryptExportKey!\n"), 
               GetLastError ());
      goto exit;
    }

    // Write the size of key BLOB to the destination file.
    fwrite (&dwKeyBlobLen, sizeof (DWORD), 1, hDestFile);

    if (ferror (hDestFile)) 
    {
      wprintf (TEXT("Error writing header!\n"));
      goto exit;
    }

    // Write the key BLOB to the destination file.
    fwrite (pbKeyBlob, 1, dwKeyBlobLen, hDestFile);

    if (ferror (hDestFile)) 
    {
      wprintf (TEXT("Error writing header!\n"));
      goto exit;
    }
  } 
  else 
  {
    // Encrypt the file with a session key derived from a password.

    // Create a hash object.
    if (!CryptCreateHash (hProv, CALG_MD5, 0, 0, &hHash)) 
    {
      wprintf (TEXT("Error %x during CryptCreateHash!\n"), 
               GetLastError ());
      goto exit;
    }

    // Hash in the password data.
    if (!CryptHashData (hHash, (PBYTE)lpszPassword, 
                        wcslen (lpszPassword), 0)) 
    {
      wprintf (TEXT("Error %x during CryptHashData!\n"), 
               GetLastError ());
      goto exit;
    }

    // Derive a session key from the hash object.
    if (!CryptDeriveKey (hProv, CALG_RC2, hHash, 0, &hKey)) 
    {
      wprintf (TEXT("Error %x during CryptDeriveKey!\n"), 
               GetLastError ());
      goto exit;
    }
  }

  // Allocate memory.
  if ((pbBuffer = malloc (BUFFER_SIZE)) == NULL) 
  {
    wprintf (TEXT("Out of memory!\n"));
    goto exit;
  }

  // Encrypt the source file and write to the destination file.
  do 
  {
    // Read up to BLOCK_SIZE bytes from the source file.
    dwCount = fread (pbBuffer, 1, BLOCK_SIZE, hSrcFile);

    if (ferror (hSrcFile)) 
    {
      wprintf (TEXT("Error reading Plaintext!\n"));
      goto exit;
    }

    bEOF = feof (hSrcFile);

    // Encrypt the data.
    if (!CryptEncrypt (hKey, 0, bEOF, 0, pbBuffer, &dwCount, 
                       BUFFER_SIZE)) 
    {
      wprintf (TEXT("bytes required:%d\n"), dwCount);
      wprintf (TEXT("Error %x during CryptEncrypt!\n"), 
               GetLastError ());
      goto exit;
    }

    // Write the data to the destination file.
    fwrite (pbBuffer, 1, dwCount, hDestFile);

    if (ferror (hDestFile)) 
    {
      wprintf (TEXT("Error writing Ciphertext!\n"));
      goto exit;
    }
  } while (!bEOF);

  bReturn = TRUE;

  wprintf (TEXT("OK\n"));

exit:

   // Close the files.
  if (hSrcFile) 
    fclose (hSrcFile);

  if (hDestFile) 
    fclose (hDestFile);

  // Free memory.
  if (pbKeyBlob) 
    free (pbKeyBlob);

  if (pbBuffer) 
    free (pbBuffer);

  // Destroy the session key.
  if (hKey) 
    CryptDestroyKey (hKey);

  // Release the key exchange key handle.
  if (hXchgKey) 
    CryptDestroyKey (hXchgKey);

  // Destroy the hash object.
  if (hHash) 
    CryptDestroyHash (hHash);

  // Release the provider handle.
  if (hProv) 
    CryptReleaseContext (hProv, 0);

  return bReturn;
}

If a message was encoded for a particular user, the CryptGenKey function was used to create a random session key before the encryption was performed. Before decoding the message, the key BLOB containing the session key must be imported into the CSP with the CryptImportKey function. This function uses the user's key exchange private key to decode the key BLOB and ensure that the originating key BLOB was created using the matching key exchange public key.

If the message was encoded so that any password holder can access the data, CryptImportKey is not used. Instead, create the decryption session key with the CryptDeriveKey function. You also need to supply the function with the password or other access token.

The session key's parameters must be configured in the same way as when the encryption was performed. These parameters can be specified using the CryptSetKeyParam function. For example, if the salt value was changed one or more times during the encryption process, it must also be changed during the decryption process in exactly the same manner.

The message is decoded using the CryptDecrypt function. If the message is too large to fit comfortably in memory, it can be decoded in sections, through multiple calls to CryptDecrypt.

When the decryption is complete, be sure to destroy the session key, using the CryptDestroyKey function. In addition to destroying the key, this frees CSP resources.

The following code example decodes the file created by the previous example of encryption. This decryption example uses the RC2 block cipher and writes out the plaintext data to a file named Test2.txt. The session key used to perform the decryption is read from the cipher file.

#include <windows.h>
#include <stdio.h>
#include <wincrypt.h>
   
#define BLOCK_SIZE            1000
#define BUFFER_SIZE           1008

BOOL DecryptFile (LPTSTR, LPTSTR, LPTSTR);

/***********************************************************************

  WinMain

***********************************************************************/
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    LPTSTR lpCmdLine, int nCmdShow)                   
{
  LPTSTR lpszSource  = TEXT("test.xxx");
  LPTSTR lpszDestination = TEXT("test2.txt");
  LPTSTR lpszPassword  = TEXT("password");
 
  if (!DecryptFile (lpszSource, lpszDestination, lpszPassword)) 
  {
    wprintf (TEXT("Error encrypting file!\n"));
    return 1;
  }

  return 0;
}

/***********************************************************************

  DecryptFile

***********************************************************************/
BOOL DecryptFile (LPTSTR lpszSource, LPTSTR lpszDestination, 
                  LPTSTR lpszPassword)
{
  FILE *hSrcFile = NULL, 
       *hDestFile = NULL;

  HCRYPTPROV hProv = 0;
  HCRYPTHASH hHash = 0;
  HCRYPTKEY hKey = 0;

  PBYTE pbBuffer = NULL, 
        pbKeyBlob = NULL;

  BOOL bEOF = 0, 
       bReturn = FALSE;

  DWORD dwCount, 
        dwKeyBlobLen;
  
  // Open the source file.
  if ((hSrcFile = _wfopen (lpszSource, TEXT("rb"))) == NULL) 
  {
    wprintf (TEXT("Error opening Ciphertext file!\n"));
    goto exit;
  }

  // Open the destination file.
  if ((hDestFile = _wfopen (lpszDestination, TEXT("wb"))) == NULL) 
  {
    wprintf (TEXT("Error opening Plaintext file!\n"));
    goto exit;
  }

  // Get the handle to the default provider.
  if (!CryptAcquireContext (&hProv, NULL, NULL, PROV_RSA_FULL, 0)) 
  {
    wprintf (TEXT("Error %x during CryptAcquireContext!\n"), 
             GetLastError ());
    goto exit;
  }

  if (lpszPassword == NULL) 
  {
    // Decrypt the file with the saved session key.

    // Read key BLOB length from the source file and allocate memory.
    fread (&dwKeyBlobLen, sizeof (DWORD), 1, hSrcFile);

    if (ferror (hSrcFile) || feof (hSrcFile)) 
    {
      wprintf (TEXT("Error reading file header!\n"));
      goto exit;
    }

    if ((pbKeyBlob = (PBYTE)malloc (dwKeyBlobLen)) == NULL) 
    {
      wprintf (TEXT("Out of memory or improperly formatted source ")
               TEXT("file!\n"));
      goto exit;
    }

    // Read the key BLOB from source file.
    fread (pbKeyBlob, 1, dwKeyBlobLen, hSrcFile);

    if (ferror (hSrcFile) || feof (hSrcFile)) 
    {
      wprintf (TEXT("Error reading file header!\n"));
      goto exit;
    }

    // Import the key BLOB into the CSP.
    if (!CryptImportKey (hProv, pbKeyBlob, dwKeyBlobLen, 0, 0, &hKey)) 
    {
      wprintf (TEXT("Error %x during CryptImportKey!\n"), 
               GetLastError ());
      goto exit;
    }
  } 
  else
  {
    // Decrypt the file with a session key derived from a password.

    // Create a hash object.
    if (!CryptCreateHash (hProv, CALG_MD5, 0, 0, &hHash)) 
    {
      wprintf (TEXT("Error %x during CryptCreateHash!\n"), 
               GetLastError ());
      goto exit;
    }

    // Hash in the password data.
    if (!CryptHashData (hHash, (PBYTE)lpszPassword, 
                        wcslen (lpszPassword), 0)) 
    {
      wprintf (TEXT("Error %x during CryptHashData!\n"), 
               GetLastError ());
      goto exit;
    }

    // Derive a session key from the hash object.
    if (!CryptDeriveKey (hProv, CALG_RC2, hHash, 0, &hKey)) 
    {
      wprintf (TEXT("Error %x during CryptDeriveKey!\n"), 
               GetLastError ());
      goto exit;
    }
  }

  // Allocate memory.
  if ((pbBuffer = (PBYTE)malloc (BUFFER_SIZE)) == NULL) 
  {
    wprintf (TEXT("Out of memory!\n"));
    goto exit;
  }

  // Decrypt the source file and write to the destination file.
  do 
  {
    // Read up to BLOCK_SIZE bytes from the source file.
    dwCount = fread (pbBuffer, 1, BLOCK_SIZE, hSrcFile);

    if (ferror (hSrcFile)) 
    {
      wprintf (TEXT("Error reading Ciphertext!\n"));
      goto exit;
    }

    bEOF = feof (hSrcFile);

    // Decrypt the data.
    if (!CryptDecrypt (hKey, 0, bEOF, 0, pbBuffer, &dwCount)) 
    {
      wprintf (TEXT("Error %x during CryptDecrypt!\n"), 
               GetLastError ());
      goto exit;
    }

    // Write the data to the destination file.
    fwrite (pbBuffer, 1, dwCount, hDestFile);

    if (ferror (hDestFile)) 
    {
      wprintf (TEXT("Error writing Plaintext!\n"));
      goto exit;
    }
  } while (!bEOF);

  bReturn = TRUE;

  wprintf (TEXT("OK\n"));

exit:

  // Close the source files.
  if (hSrcFile) 
    fclose (hSrcFile);

  // Close the destination files.
  if (hDestFile) 
    fclose (hDestFile);

  // Free memory.
  if (pbKeyBlob) 
    free (pbKeyBlob);

  // Free memory.
  if (pbBuffer) 
    free (pbBuffer);

  // Destroy the session key.
  if (hKey) 
    CryptDestroyKey (hKey);

  // Destroy the hash object.
  if (hHash) 
    CryptDestroyHash (hHash);

  // Release the provider handle.
  if (hProv) 
    CryptReleaseContext (hProv, 0);

  return bReturn;
}