Encrypting and decrypting sensitive data using CryptoAPI

Many programs deal with sensitive data that has to be stored in memory, the windows registry or a file in the disk. It is common for programmers to “invent” an encryption algorithm which most of the time is very weak. However, the Win32 API provides us with the ability to use pretty good encryption algorithms such as RC4, which is included in NT. In fact CryptoAPI allows the use of anyencryption algorithm, providing it is currently installed in the system by some security provider. Unfortunately, CryptoAPI is not supported in Windows 95 or Windows 98, it works on NT only. In this article we show a simple way to encrypt and decrypt a string that is stored in the windows registry using CryptoAPI functions.

To ensure that the default cryptographic client is correctly setup an application may call the following function, which returns TRUE if the client is correctly installed and FALSE otherwise:


BOOL SetupCryptoClient()
{
	// Ensure that the default cryptographic client is set up.
	HCRYPTPROV hProv;
	HCRYPTKEY hKey;

	// Attempt to acquire a handle to the default key container.
	if (!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, 0))
	{
		// Some sort of error occured, create default key container.
		if (!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET))
		{
			// Error creating key container!
			return FALSE;
		}
	}

	// Attempt to get handle to signature key.
	if (!CryptGetUserKey(hProv, AT_SIGNATURE, &hKey))
	{
		if (GetLastError() == NTE_NO_KEY)
		{
			// Create signature key pair.
			if (!CryptGenKey(hProv, AT_SIGNATURE, 0, &hKey))
			{
				// Error during CryptGenKey!
				CryptReleaseContext(hProv, 0);
				return FALSE;
			}
			else
			{
				CryptDestroyKey(hKey);
			}
		}
		else
		{
			// Error during CryptGetUserKey!
			CryptReleaseContext(hProv, 0);
			return FALSE;
		}
	}

	// Attempt to get handle to exchange key.
	if (!CryptGetUserKey(hProv,AT_KEYEXCHANGE,&hKey))
	{
		if (GetLastError()==NTE_NO_KEY)
		{
			// Create key exchange key pair.
			if (!CryptGenKey(hProv,AT_KEYEXCHANGE,0,&hKey))
			{
				// Error during CryptGenKey!
				CryptReleaseContext(hProv, 0);
				return FALSE;
			}
			else
			{
				CryptDestroyKey(hKey);
			}
		}
		else
		{
			// Error during CryptGetUserKey!
			CryptReleaseContext(hProv, 0);
			return FALSE;
		}
	}

	CryptReleaseContext(hProv, 0);
	return TRUE;
}

A commonly used feature for many programs is a password. Often it has to be saved to the registry and read later from there. Note that this is an inherently unsecured procedure, the registry itself must be protected using NT ACLs. The password must be saved encrypted with an encryption key. In the following code it is a fixed local variable, you would prefer to make it a non-fixed global value. This is how the password is encrypted and saved to the registry:


BOOL SavePasswordToRegistry(TCHAR* szPassword)
{
	BOOL bResult = TRUE;

	TCHAR szKey[256];
	HKEY hRegKey = NULL;
	_tcscpy(szKey, _T("SOFTWARE\Your Company\Your Program\"));

	if (RegCreateKey(HKEY_CURRENT_USER, szKey, &hRegKey) != ERROR_SUCCESS)
		return FALSE;

	HCRYPTPROV hProv = NULL;
	HCRYPTKEY hKey = NULL;
	HCRYPTKEY hXchgKey = NULL;
	HCRYPTHASH hHash = NULL;
	DWORD dwLength;
	// Used to encrypt the real password
	TCHAR szLocalPassword[] = _T("Mz6@a0i*");

	// Get handle to user default provider.
	if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0))
	{
		// Create hash object.
		if (CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
		{
			// Hash password string.
			dwLength = sizeof(TCHAR)*_tcslen(szLocalPassword);
			if (CryptHashData(hHash, (BYTE *)szLocalPassword, dwLength, 0))
			{
				// Create block cipher session key based on hash of the password.
				if (CryptDeriveKey(hProv, CALG_RC4, hHash, CRYPT_EXPORTABLE, &hKey))
				{
					// Determine number of bytes to encrypt at a time.
					dwLength = sizeof(TCHAR)*_tcslen(szPassword);

					// Allocate memory.
					BYTE *pbBuffer = (BYTE *)malloc(dwLength);
					if (pbBuffer != NULL)
					{
						memcpy(pbBuffer, szPassword, dwLength);
						// Encrypt data
						if (CryptEncrypt(hKey, 0, TRUE, 0, pbBuffer, &dwLength, dwLength))
						{
							// Write data to registry.
							DWORD dwType = REG_BINARY;
							// Add the password.
							if (::RegSetValueEx(hRegKey, _T("Password"), 0, REG_BINARY, pbBuffer, dwLength)!=ERROR_SUCCESS)
							{
								bResult = FALSE;
							}
							::RegCloseKey(hRegKey);
						}
						else
						{
							bResult = FALSE;
						}
						// Free memory.
					  free(pbBuffer);
					}
					else
					{
						bResult = FALSE;
					}
					CryptDestroyKey(hKey);  // Release provider handle.
				}
				else
				{
					// Error during CryptDeriveKey!
					bResult = FALSE;
				}
			}
			else
			{
				// Error during CryptHashData!
				bResult = FALSE;
			}
			CryptDestroyHash(hHash); // Destroy session key.
		}
		else
		{
			// Error during CryptCreateHash!
			bResult = FALSE;
		}
		CryptReleaseContext(hProv, 0);
	}

	return bResult;
}

The following function reads the password from the registry and decrypts it. Note that the szPassword parameter should be already allocated with a minimum size of 32 characters (64 bytes if using UNICODE).


BOOL ReadPasswordFromRegistry(TCHAR* szPassword)
{
	BOOL bResult = TRUE;

	TCHAR szKey[256];
	HKEY hRegKey = NULL;
	_tcscpy(szKey, _T("SOFTWARE\Your Company\Your Program\"));

	if (RegOpenKeyEx(HKEY_CURRENT_USER, szKey, 0, KEY_QUERY_VALUE, &hRegKey) == ERROR_SUCCESS)
	{
		HCRYPTPROV hProv = NULL;
		HCRYPTKEY hKey = NULL;
		HCRYPTKEY hXchgKey = NULL;
		HCRYPTHASH hHash = NULL;
		DWORD dwLength;
		// has to be the same used to encrypt!
		TCHAR szLocalPassword[] = _T("Mz6@a0i*");

		// Get handle to user default provider.
		if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0))
		{
			// Create hash object.
			if (CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
			{
				// Hash password string.
				dwLength = sizeof(TCHAR)*_tcslen(szLocalPassword);
				if (CryptHashData(hHash, (BYTE *)szLocalPassword, dwLength, 0))
				{
					// Create block cipher session key based on hash of the password.
					if (CryptDeriveKey(hProv, CALG_RC4, hHash, CRYPT_EXPORTABLE, &hKey))
					{
						// the password is less than 32 characters
						dwLength = 32*sizeof(TCHAR);
						DWORD dwType = REG_BINARY;
						if (RegQueryValueEx(hRegKey, _T("Password"), NULL, &dwType, (BYTE*)szPassword, &dwLength)==ERROR_SUCCESS)
						{
							if (!CryptDecrypt(hKey, 0, TRUE, 0, (BYTE *)szPassword, &dwLength))
								bResult = FALSE;
						}
						else
						{
							bResult = FALSE;
						}
						CryptDestroyKey(hKey);  // Release provider handle.
					}
					else
					{
						// Error during CryptDeriveKey!
						bResult = FALSE;
					}
				}
				else
				{
					// Error during CryptHashData!
					bResult = FALSE;
				}
				CryptDestroyHash(hHash); // Destroy session key.
			}
			else
			{
				// Error during CryptCreateHash!
				bResult = FALSE;
			}
			CryptReleaseContext(hProv, 0);
		}
		::RegCloseKey(hRegKey);
	}
	else
		bResult = FALSE;

	return bResult;
}

Now to save a password (any string) encrypted to the registry we should do:


TCHAR szPassword[32];
_tcscpy(szPassword, _T("The Password"));
if (SavePasswordToRegistry(szPassword))
	// succeeded
else
	// failed

To read and unencrypt the password from the registry:


TCHAR szPassword[32];
if (ReadPasswordFromRegistry(szPassword))
	// succeeded, password is in szPassword
else
	// failed

In this sample the location within the registry is fixed. It is not difficult to modify the functions so they receive the registry location as a parameter. Note that security related parameters should be saved in special registry locations, with limited access.

Download demo project – 6 KB

Date Last Updated: April 4, 1999

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read