An AES Encrypting Registry Class

Introduction

The CAESEncRegKey class addresses the occasional problem of securely saving data to the Registry. This article presents Yet Another Registry Class that uses AES to encrypt data.

Currently, there are three FIPS-approved symmetric encryption algorithms: AES, Triple DES, and Skipjack. The class presented uses AES or the Advanced Encryption Standard in CBC Mode.

Note: On May 19, 2005, DES (FIPS 46-3) was withdrawn, and is no longer approved for Federal use.

The underlying Cryptographic library is Wei Dai's Crypto++. If required, Crypto++ is FIPS 140-2 conformant. Using the conformant version of the library requires the programmer to load the library as a DLL.

Other cryptographic libraries exist, such as Peter Guttman's Cryptlib. The reader is encouraged to modify the program presented in this article to include other libraries.

Background

Windows maintains a secure area of the Registry called the SAM (Security Accounts Manager). Users, administrators, and programmers are generally not permitted access to this area of the Registry directly. One must use API functions such as the LSA family, or tools such as User Manager for Domains or Active Directory Users and Computers.

To allow programmers to securely save data to the Registry (but not the SAM), a programmer can use CAESEncRegKey. However, there are a few limitations that one must observe when using the Registry. The most important for the purposes of this article is limiting binary data (value type REG_BINARY) size at or below 2048 bytes. See Microsoft's Registry Element Size Limit in MSDN.

Downloads

This article includes four downloads:

  • GUI demo
  • CLI demo
  • Source Code
  • Key and IV Generator Program

The GUI demo is a release build demonstration that exercises the CAESEncRegKey class. The CLI (command line) demo is an AES proof of concept using the Crypto++ library. It simply demonstrates the Crypto++ AES Encryption/Decryption process.

The Key and IV Generator program uses the Crypto++ AutoSeededRandomPool PRNG to create pseudo random values for the Key and IV vectors.

Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment

Please see the related article, Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment.

An AES Encrypting Registry Class

Understanding the Crypto++ Encryption/Decryption Process

One of the easiest ways to approach this class file is by inspecting the CLI demo (shown below). The code performs the following:

  1. Construct a AES::Encryption object (seeded with a Key).
  2. Construct a CBC_Mode_ExternalCipher::Encryption object (seeded with an encryptor (AES::Encryption), and an IV).
  3. Construct a StreamTransformationFilter for buffering and padding (seeded with a cipher (CBC_Mode_ExternalCipher::Encryption) and std::string).
  4. Push the plain text into the StreamTransformationFilter (which places the encrypted text in ciphertext).

When encrypting the string, length( ) + 1 is used so that the trailing '\0' is captured (used later for decryption). The encrypted text is available in the std::string object ciphertext. Because the ciphertext may have embedded '\0' characters, use member functions std::string::size( ) or std::string::length( ) rather than std::strlen( ). SGI states that std::string::length( ) is synonymous with std::string::size( ). The raw data can be accessed through the std::string::c_str( ) or std::string::data( ) function. See the SGI documentation for basic_string for a detailed discussion of the functions.

Decryption is simply the reverse of encryption, using AES::Decryption and CBC_Mode_ExternalCipher::Decryption objects.

#include <iostream>

#include "aes.h"
#include "modes.h"
#include "filters.h"

#ifdef _DEBUG
#  pragma comment ( lib, "cryptlibd" )
#else
#  pragma comment ( lib, "cryptlib" )
#endif

int main(int argc, char* argv[]) {

   // Key and IV setup
   byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ],
      iv[ CryptoPP::AES::BLOCKSIZE ];
   memset( key, 0x00, CryptoPP::AES::DEFAULT_KEYLENGTH );
   memset( iv, 0x00, CryptoPP::AES::BLOCKSIZE );

   // String and Sink setup
   std::string plaintext = "Now is the time for all good men to
                            come to the aid...";
   std::string ciphertext;
   std::string decryptedtext;

   // Dump Plain Text
   std::cout << "Plain Text (" << plaintext.size() <<" bytes)"
             << std::endl;
   std::cout << plaintext;
   std::cout << std::endl <<std::endl;

   // Create Cipher Text
   CryptoPP::AES::Encryption aesEncryption(key,
      CryptoPP::AES::DEFAULT_KEYLENGTH);
   CryptoPP::CBC_Mode_ExternalCipher::Encryption
      cbcEncryption( aesEncryption, iv );

   CryptoPP::StreamTransformationFilter stfEncryptor(cbcEncryption,
      new CryptoPP::StringSink( ciphertext ) );
   stfEncryptor.Put( reinterpret_cast<const unsigned char*>
      ( plaintext.data() ), plaintext.length() + 1 );
   stfEncryptor.MessageEnd();

   // Dump Cipher Text
   std::cout << "Cipher Text (" << ciphertext.size() << " bytes)"
      << std::endl;
   for( int i = 0; i < ciphertext.size(); i++ ) {
      std::cout << (0xFF & (unsigned int)ciphertext[i]) << " ";
   }
   std::cout << std::endl << std::endl;

   // Decrypt
   CryptoPP::AES::Decryption aesDecryption(key,
      CryptoPP::AES::DEFAULT_KEYLENGTH);
   CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption
     ( aesDecryption, iv );

   CryptoPP::StreamTransformationFilter stfDecryptor
     (cbcDecryption, new CryptoPP::StringSink( decryptedtext ) );
   stfDecryptor.Put( reinterpret_cast<const unsigned char*>
     ( ciphertext.data() ), ciphertext.size() );
   stfDecryptor.MessageEnd();

   // Dump Decrypted Text
   std::cout << "Decrypted Text: " << std::endl;
   std::cout << decryptedtext;
   std::cout << std::endl << std::endl;

   return 0;
}

CAESEncRegKey Class

The CAESEncRegKey includes member functions for reading and writing of both encrypted and unencrypted data. The supported registry value types are REG_BINARY, REG_DWORD, and REG_SZ. The following will discuss the operation of writing and reading of an encrypted string. DWORD and Binary data are similar.

The class also contains data members for stateful representation of the following. Generally, most will not change once set. The exception is ValueName, which changes as multiple data is read and written.

  • AES Key
  • AES IV
  • HKEY
  • SubKey
  • ValueName

AES Key and AES IV are struct to simplify operations. SubKey and ValueName are CStrings.

In keeping with the spirit of Microsoft's Registry APIs, all functions have a return value type of LONG.

CAESEncRegKey Encryption

The steps for writing an encrypted string are as follows:

  1. WriteString(...)
  2. WriteEncString(...)
  3. EncryptData(...)
  4. WriteNonEncBinary(...)

Below is the WriteString(...) function. It will defer to WriteEncString(...) or WriteNonEncString(...) as required.

LONG CAESEncRegKey::WriteString(LPCTSTR pszData,
                                BOOL bEncrypt /*=FALSE*/) const
{
   LONG lResult = ERROR_SUCCESS;

   if( TRUE == bEncrypt ) {

      lResult = WriteEncString( pszData );

    } else {

      lResult = WriteNonEncString( pszData );
   }

   return lResult;
}

WriteNonEncString(...) simply acts like any other Registry Class—it writes the string to the Registry. Note that RegCreateKeyEx(...) is used to open keys for writing because RegCreateKeyEx(...) will either create the key or open an existing key.

WriteEncString(...) is coded as follows. Note that two operations occur:

  1. The string is encrypted using earlier code by way of EncryptData(...).
  2. The string is written to the Registry using WriteNonEncBinary(...).
LONG CAESEncRegKey::WriteEncString(LPCTSTR pszData) const
{
   LONG lResult = ERROR_SUCCESS;

   // Returned from EncryptData(...)
   BYTE* pcbEncryptedData = NULL;
   DWORD dwEncryptedSize = 0;

   // Anti-snoop it...
   lResult = EncryptData( reinterpret_cast<const BYTE*>(pszData),
                         ( ::lstrlen( pszData ) + 1 ) * sizeof( TCHAR ),
                         &pcbEncryptedData, &dwEncryptedSize );
   // Sanity Check
   if( ERROR_SUCCESS != lResult ) { goto FINISHED; }

   // Save it...
   lResult = WriteNonEncBinary( pcbEncryptedData, dwEncryptedSize );

FINISHED:

   // Cleanup...
   if( NULL != pcbEncryptedData ) { delete[] pcbEncryptedData; }

   return lResult;
}

WriteNonEncBinary(...) performs the Registry write, as if it were simply called to write unencrypted binary data. WriteNonEncBinary(...) will be called frequently because writing data that has already been encrypted is that same as writing non-encrypted data.

LONG CAESEncRegKey::WriteNonEncBinary(const BYTE *pcbData,
                                      UINT nSize) const
{
   LONG lResult = ERROR_SUCCESS;
   HKEY hKey    = NULL;

   // When writing, create the key if it does not exist
   lResult = RegCreateKeyEx( _hKey, _szSubKey, 0, NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_WRITE, NULL, &hKey, NULL );

   // Sanity Check
   if( ERROR_SUCCESS != lResult ) { goto FINISHED; }

   // Paydirt
   lResult = RegSetValueEx( hKey, _szValueName, 0, REG_BINARY, 
                            reinterpret_cast<const BYTE*>( pcbData ),
                            nSize );
FINISHED:

   // Cleanup...
   if( NULL != hKey ) { RegCloseKey( hKey ); }

   return lResult;
}

An AES Encrypting Registry Class

CAESEncRegKey Decryption

The steps for reading an encrypted string are as follows:

  1. ReadString(...)
  2. ReadEncString(...)
  3. ReadData(...)
  4. DecryptData(...)

The ReadEncString(...) function calls ReadData(...), which returns the binary data and size of the encrypted string. It is ReadEncString(...)'s responsibility to free it. Also note that ReadData(...) opens the key using RegOpenKeyEx(...); the key is not created if missing.

Once the encrypted data is available, DecryptData(...) is called. Again, the DecryptData(...) function simply decrypts the data using AES::Decryption and CBC_Mode_ExternalCipher::Decryption objects.

LONG CAESEncRegKey::ReadEncString(CString &szValue) const
{
   LONG lResult = ERROR_SUCCESS;

   // Returned from the Registry through ReadData(...)
   DWORD    dwSize  = 0;
   BYTE*    pcbData = NULL;

   // Returned from DecryptData(...)
   DWORD    dwDecryptedSize  = 0;
   BYTE*    pcbDecryptedData = NULL;

   // Read From the Registry
   lResult = ReadData( &pcbData, &dwSize );

   // Sanity Check
   if( ERROR_SUCCESS != lResult ) { goto FINISHED; }

   // Undo it...
   lResult = DecryptData( pcbData, dwSize, &pcbDecryptedData,
                          &dwDecryptedSize );

   // Sanity Check
   if( ERROR_SUCCESS != lResult ) { goto FINISHED; }

   // Paydirt...
   szValue = reinterpret_cast<const TCHAR*>( pcbDecryptedData );

FINISHED:

   // Cleanup...
   if( NULL != pcbData ) { delete[] pcbData; }
   if( NULL != pcbDecryptedData ) { delete[] pcbDecryptedData; }

   return lResult;
}

Finally, when reading data, CAESEncRegKey mimics the functionality of RegQueryValueEx(...) by returning ERROR_SUCCESS or ERROR_MORE_DATA depending on the cbBuffer and cbSize arguments. See RegQueryValueEx in MSDN for a detailed explanation. Below is a sample from ReadEncBinary(...). Note that the data must be read from the Registry, and then decrypted before the buffer size can be determined.

LONG CAESEncRegKey::ReadEncBinary(BYTE* pcbData,
                                  DWORD* dwSize) const {

   ...

   // Read From the Registry
   lResult = ReadData( &pcbRegistryData, &dwRegistrySize );

   ...

   // Just Undo It...
   lResult = DecryptData( pcbRegistryData, dwRegistrySize,
                          &pcbDecryptedData, &dwDecryptedSize );
   ...

   // Emulate the RegQueryValue(...) Function
   if( *dwSize < dwDecryptedSize && NULL != pcbData ) {

      lResult = ERROR_MORE_DATA;
      *dwSize = dwDecryptedSize;
      goto FINISHED;
   }

   // Emulate the RegQueryValue(...) Function
   if( *dwSize < dwDecryptedSize && NULL == pcbData ) {

      lResult = ERROR_SUCCESS;
      *dwSize = dwDecryptedSize;
      goto FINISHED;
   }

   // Inform Caller of size of pcbData
   *dwSize = dwDecryptedSize;

   ...

   return lResult;
}

Using CAESEncRegKey

CAESEncRegKey not only provides functions for reading and writing of registry value types, it also provides mutators and accessors for changing the Registry Key object. As stated earlier, two helper structures are used to manage the AES Key and AES IV. The structures are declared and defined in aeshelper.h.

The following three examples shows object construction.

// Create an default object
   CAESEncRegKey aesKey;

   // Create an object setting Registry paths
   CAESEncRegKey aesKey( HKEY_LOCAL_MACHINE,
                         _T("\\Software\\Code Guru"),
                         _T("Value Name") );

   // Create an object setting Key and IV vectors
   BYTE key[ 16 ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
   BYTE  iv[ 16 ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };
   CAESEncRegKey aesKey( key, 16, iv, 16 );

If you are using the default constructor, set the five data members before invoking a WriteXXX(...) function:

CAESEncRegKey aesKey;
BYTE key[ 16 ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE  iv[ 16 ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };

aesKey.SetKey( key, CryptoPP::AES::DEFAULT_KEYLENGTH );
aesKey.SetIV (  iv, CryptoPP::AES::BLOCKSIZE );

aesKey.Set.SetHKEY( GetHKey() );

aesKey.SetSubKey( GetSubKey() );
aesKey.SetValueName( GetStringValueName() );

aesKey.WriteString( GetStringData(), TRUE );

Reading data from the Registry is simply the reverse operation:

CAESEncRegKey aesKey;
BYTE key[ 16 ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE  iv[ 16 ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };

aesKey.SetKey( key, CryptoPP::AES::DEFAULT_KEYLENGTH );
aesKey.SetIV (  iv, CryptoPP::AES::BLOCKSIZE );

aesKey.SetHKEY( GetHKey() );

aesKey.SetSubKey( GetSubKey() );
aesKey.SetValueName( GetStringValueName() );

CString szData;

aesKey.ReadString( szData, TRUE );

Reading multiple key values would be accomplished as follows:

CAESEncRegKey aesKey;
CString szData;
BYTE key[ 16 ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE  iv[ 16 ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };

...

aesKey.SetValueName( _T("Value 1") );
aesKey.ReadString( szData, TRUE );
// Do something with retrieved data

aesKey.SetValueName( _T("Value 2") );
aesKey.ReadString( szData, TRUE );
// Do something with retrieved data

aesKey.SetValueName( _T("Value 3") );
aesKey.ReadString( szData, TRUE );
// Do something with retrieved data  

Finally, the code from the driver program which verifies reading and writing of encrypted Binary data:

void CRegistryEncryptionDlg::OnTestBinary()
{
   BYTE data[ MAX_REG_BINARY_SIZE ];
   CryptoPP::AutoSeededRandomPool rng;

   rng.GenerateBlock( data, MAX_REG_BINARY_SIZE );

   _AESEncRegKey.SetHKEY( GetHKey() );
   _AESEncRegKey.SetSubKey( GetSubKey() );
   _AESEncRegKey.SetValueName( GetBinaryValueName() );

   _AESEncRegKey.WriteBinary( data, MAX_REG_BINARY_SIZE, TRUE );

   DWORD dwSize = MAX_REG_BINARY_SIZE;
   BYTE  decrypted[ MAX_REG_BINARY_SIZE ];

   _AESEncRegKey.ReadBinary( decrypted, &dwSize, TRUE );

   if( 0 == memcmp( data, decrypted, MAX_REG_BINARY_SIZE ) ) {

      CWnd::MessageBox( _T("Binary Encryption Test Passed."),
                        _T("Binary Encryption Test"), MB_OK |
                        MB_ICONASTERISK );

   } else {

      CWnd::MessageBox( _T("Binary Encryption Test Failed."),
                        _T("Binary Encryption Test"), MB_OK |
                        MB_ICONERROR );
   }
}

Crypto++ and VisualC++ Compatibility

When linking cryptlib.lib (or cryptlibd.lib) to a MFC project, the following may ease the use of the Crypto++ library. Place the following in stdafx.h:

#ifdef _UNICODE
#  pragma comment( linker, "/ENTRY:wWinMainCRTStartup")
#endif

#pragma warning(disable: 4786)
#pragma warning(disable: 4231)

// Crypto++ Library
#ifdef _DEBUG
#  pragma comment ( lib, "cryptlibd" )
#else
#  pragma comment ( lib, "cryptlib" )
#endif

// Debug Libraries
// #pragma comment( linker, "/NODEFAULTLIB:msvcprtd.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:libcmtd.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:msvcrtd.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:libcd.lib" )

// Release Libraries
// #pragma comment( linker, "/NODEFAULTLIB:msvcprt.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:libcmt.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:msvcrt.lib" )
// #pragma comment( linker, "/NODEFAULTLIB:libc.lib" )

If LNK1104 errors are encountered, re-run Visual Studio setup and install the MFC Unicode libraries.

[msvs_unicode.gif]

If one encounters LNK2005 'Default Library' errors, verify the MFC project is:

  • Using MFC as a Static Library
  • Using Multithreaded MFC Libraries

If that fails, uncomment the appropriate #pragma statement in stdafx.h. See Carl Daniel's comment on the error from Google's Usenet archive.

History

  • 11/14/2006: Updated Crypto++ Link
  • 11/14/2006: Updated Article Graphics
  • 10/10/2005: Initial Release


About the Author

Jeffrey Walton

In the past, I have worked as an IT consultant for County Government (Anne Arundel County), the Nuclear Energy Institute, the Treasury Department, and Social Security Administration as a Network Engineer and System Administrator. Primary Administration experience includes Microsoft Windows and Novell Netware, with additional exposure and familiarity with Mac and Linux OSes. Previous to the US government, I was a programmer for a small business using Microsoft Visual Languages (Basic 5.0, 6.0, and C++ 5.0, 6.0) and Scripting Languages. An undergraduate degree (BS in Computer Science) was obtained from University of Maryland, Baltimore County. Graduate work includes a Masters of Science (Computer Science) from Johns Hopkins University (expected before 2009). Training and Certifications include Microsoft, Checkpoint, and Cisco.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

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

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds