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



Comments

  • Compile errors on everything

    Posted by Legacy on 07/17/2002 12:00am

    Originally posted by: Dins

    everything wincrypt object I declare returns a compile error in VC++... ONCE i removed the LEAN define from my stdafx.h file and it compiled but then the next time I wanted to use it it was back to the same crap and editting the afx file did nothing.

    Reply
  • Memory leaks in SetupCryptoClient

    Posted by Legacy on 08/13/2001 12:00am

    Originally posted by: fima

    SetupCryptoClient is missing 2 calls
    to CryptDestroyKey(hKey);

    after each successful CryptGetUserKey.

    This creates a memory leak...

    Fima.

    Reply
  • Fix for NT/Win2k/98/ME/IIS

    Posted by Legacy on 07/09/2001 12:00am

    Originally posted by: DevGuy

    CryptAcquireContext can fail in many ways, for example, when code is invoked by an ASP page.  Here is the fix.
    
    

    BOOL bSuccess = CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL,
    0);

    if (!bSuccess)
    bSuccess = CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL,
    CRYPT_MACHINE_KEYSET);

    // create a new context

    if (!bSuccess)
    {
    bSuccess = CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET);
    }

    if (!bSuccess)
    {
    bSuccess = CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET|CRYPT_MACHINE_KEYSET);
    }


    Reply
  • how to use CryptoAPI within MFC application ?

    Posted by Legacy on 10/25/2000 12:00am

    Originally posted by: Joe

    I try to use CryptoAPI for Windows (MFC) application,
    
    however, I got compile errors ( w98/w2000, vc6),
    " CryptoDoc.cpp(263) : error 065: 'CryptAcquireContext' : undeclared identifier ". If anyone knew the solution,
    please give me a help.

    Reply
  • Avoid EOF in encrypted data??

    Posted by Legacy on 08/12/2000 12:00am

    Originally posted by: Quakey

    Hi there,
    I tried this CryptoAPI and it works perfectly. However,
    since I save the encrypted data to a file and read it back
    at a later time, an EOF in the data would totally cripple
    the fil i/o involved in reading the data. So how do I
    avoid the EOF in encrypted data?? Thanks!

    Reply
  • Doesn't work for french NT

    Posted by Legacy on 04/27/2000 12:00am

    Originally posted by: PIALAT Richard

    Due to French legislation and as expressed in CryptEncrypt function help, this function will fail with a NTE_PERM error code.

    Reply
  • CryptoAPI on 40 and 128 bits work on 95, 98

    Posted by Legacy on 12/31/1999 12:00am

    Originally posted by: Marian Miulet

    CryptoAPI works fine with Win95 and Win98 as soon as you have IE3 or later.
    For 128 bits you need a special patch from Microsoft that can be installed only if IE is already installed.
    Another way for CryptoAPI 128 bits is a little work done in the registry but you need rsaenh.dll (in system directory) that is available only in USA and Canada.
    Hope this helps.

    REGEDIT4

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Enhanced Cryptographic Provider v1.0]
    "Image Path"="rsaenh.dll"
    "Type"=dword:00000001
    "Signature"=hex:5e,50,ac,2d,eb,1e,54,68,b0,bc,c7,ec,86,40,8d,65,f7,2b,89,30,6a,\
    b4,a7,8e,44,de,03,69,97,6e,84,ee,40,21,96,46,26,5a,0d,81,e5,88,fa,77,09,c9,\
    64,1f,db,d5,98,d9,e9,ed,92,08,e8,89,40,47,2c,ab,a6,83,68,bc,f3,f0,4c,d7,07,\
    52,1a,32,89,95,33,98,07,6b,d2,3f,cd,fa,db,a1,0e,f6,19,99,64,01,1f,ab,3c,ed,\
    be,9e,36,92,74,ec,f4,a1,78,42,43,d3,8b,62,79,6f,f4,1d,18,7b,1b,11,04,4f,50,\
    2e,e0,1b,a7,54,25,64,00,00,00,00,00,00,00,00


    Reply
  • Can it apply to PGP crypto

    Posted by Legacy on 08/20/1999 12:00am

    Originally posted by: Dmitry Semenov

    I wonder, can it apply to PGP encryption system.
    I have PGP installed on my machine.
    Can I use CriptoApy with PGP key and all?

    Reply
  • NULL Characters in encrypted string?

    Posted by Legacy on 07/07/1999 12:00am

    Originally posted by: Matt

    Is there any chance that the encrypted string would include a NULL character?
    In this code this is not a problem, as the call to egQueryValueEx() would return the size of the data.

    However, NULL characters would be a problem if the encrypted string was stored in a string (as I am doing). When I go to read this string for decrypting, I would miss data if there was a NULL character.

    I know a way around this - just store the length of the encrypted string - but I do not want to do this unless I have to.

    Thanks,
    Matt Shumaker
    mshumake@trsystems.com

    Reply
  • Win95 and 98 are supported by CryptoAPI

    Posted by Legacy on 04/08/1999 12:00am

    Originally posted by: John Munsch

    Trust me, I have programs that run on them.

    You just need to install IE 4.0 or above and it will install
    the required APIs. IE uses the Crypto API to do it's own
    40 bit or 128 bit encryption. The version of IE you install
    (the high security version, or low security downloadable
    from everywhere version) determines which version of the
    Crypto API will be installed.

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds