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

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • Live Event Date: April 22, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Database professionals — whether developers or DBAs — can often save valuable time by learning to get the most from their new or existing productivity tools. Whether you're responsible for managing database projects, performing database health checks and reporting, analyzing code, or measuring software engineering metrics, it's likely you're not taking advantage of some of the lesser-known features of Toad from Dell. Attend this live …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds