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 any encryption algorithm, providing it is currently installed in the system by some security provider. 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.


Last updated: 13 March 1999



Comments

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • The rapid evolution of enterprise storage technologies, combined with external forces, like the explosion of big data, can cause Linux® and server administrators to play catch-up when it comes to storage. Running a bunch of monolithic storage devices and proprietary, disconnected technologies forces administrators to spend valuable time creating and managing complex solutions. To reduce complexity and enable rapid deployment of new technologies and applications, server administrators need a single open …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds