Introduction
The CAESEncRegKey
class addresses the occasional problem of securely saving data to the registry. This article presents yet another registry class which encrypts data using AES. The class presented here uses AES or the Advanced Encryption Standard in CBC Mode.
Currently, NIST has approved three symmetric encryption algorithms for use in Federal processing: AES, Triple DES, and Skipjack. AES is specified in FIPS-197. AES was originally known as Rijndeal (pronounced "Rhine dahl"), which was the work of Joan Daemen and Vincent Rijmen — hence the portmanteau Rijndael. AES is a block cipher that accepts key varying lengths.
The DES algorithm was introduced in 1976 by FIPS-46. In 1988 DES underwent it's first revision, resulting in FIPS-46-1. FIPS-46-2 followed in 1993. Triple DES (TDES) was approved in 1998 in FIPS-46-3. Then in May 2005, DES and Triple DES were withdrawn as standards. Due to popularity in Federal circles, Triple DES received a stay of execution until 2030 in NIST SP800-67.
Finally Skipjack is specified in FIPS-185. FIPS-185 specifies the Escrowed Encryption Standard. FIPS-185 is the framework which allows decryption of an encrypted telecommunication stream intercepted by law enforcement or other jurisdictional agency.
Crypto++
The underlying cryptographic library is Wei Dai's Crypto++. For more information on using Crypto++, see Integrating Crypto++ into the Microsoft Visual C++ Environment. If you are new to the library, please take the time to familiarize yourself. 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. The SAM contains information such as cached user login information and local accounts.
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.
Using the built in Windows Scheduler (at.exe) from the command line, we schedule a launch of regedit.exe so that we can explore the SAM tree. Because the scheduler runs under NT_AUTHORITY, regedit will be launched with built in System rights and privileges.
Downloads
This article has four downloads. The GUI demo is a release build demonstration which exercises the CAESEncRegKey
class. The CLI 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. For guidelines on random number generator usage, see A Survey of Pseudo Random Number Generators.
The final sample exercises the encrypting registry class.
Advanced Encryption Standard
AES was standardized by NIST in November 2001. AES is a 128 bit block cipher that accepts key lengths of 128, 192, and 256 bits. There are seven modes of operation approved for AES. Modes of operation specify how the output of one stage (for example stage i) is used as input to the subsequent stage (for example, i+1). Because all modes (except ECB) are used in a feedback mechanism, modes require an Intitialization Vector: at iteration 0, there is no feedback from iteration -1.
FIPS-81 and SP800-38A provides five modes for confidentiality: ECB, CBC, CFB, OFB, and CTR. Confidentiality is what generally we think of when we refer to encryption. Authentication is provided by CMAC specified in SP800-38A. CCM mode of SP800-38C is used when both confidentiality and authentication are desired (CCM is sometimes referred to as CBC-MAC).
Finally, ANSI X9.52 specifies seven modes. Four modes are equivalent to FIPS-81: ECB, CBC, CFB, and OFB modes. The remaining three modes in are variants of the CBC, CFB, and OFB modes.
Crypto++ Encryption and Decryption Process
Please see Applied Crypto++: Block Ciphers for a complete discussion of the library's implementation of symmetric encryption algorithms. The article is an indepth look at StreamTransformationFilters, TransformationFilters, and Modes of Operation.
By using a filter attached to a Crypto++ Encryption or Decryption object, we are shielded from the buffering and padding issues associated with a block cipher such as AES.
byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ];
byte iv[ CryptoPP::AES::BLOCKSIZE ];
::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
::memset( iv, 0x01, CryptoPP::AES::BLOCKSIZE );
std::string PlainText = "Abraham Lincoln said, 'In the end, "
"it's not the years in your life that count. It's the life in your years.'";
CryptoPP::CFB_Mode< CryptoPP::AES >::Encryption
Encryptor( key, sizeof(key), iv);
CryptoPP::StringSource( PlainText, true,
new CryptoPP::StreamTransformationFilter(
Encryptor,
new CryptoPP::StringSink( CipherText )
)
);
CryptoPP::ECB_Mode< CryptoPP::AES >::Decryption
Decryptor( key, sizeof(key),iv );
CryptoPP::StringSource( CipherText, true,
new CryptoPP::StreamTransformationFilter(
Decryptor,
new CryptoPP::StringSink( RecoveredText )
)
);
CAESEncRegKey Class
The CAESEncRegKey
includes member functions for reading and writing of encrypted and unencrypted data. The class adds an additional parameter to the standard API, allowing us to specify if we would like to encrypt the data. To have a minimal impact on existing source code, the boolean value has a default value of false.
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. The class contains data members for the stateful representation of the following.
- AES Key
- AES IV
- HKEY
- SubKey
- ValueName
Generally, most will not change once set. The exception is ValueName, which changes as multiple data is read and written. AES Key and AES IV are struct
to simplify operations. SubKey and ValueName are CStrings
. In the spirit of Microsoft Registry APIs, all functions have a return value type of LONG
.
CAESEncRegKey Encryption
Below is the WriteString()
function. It will defer to WriteEncString()
or WriteNonEncString()
as required.
LONG CAESEncRegKey::WriteString(LPCTSTR pszData,
BOOL bEncrypt ) 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. RegCreateKeyEx()
is used to open keys for writing, since RegCreateKeyEx()
will either create the key or open an existing key. WriteEncString()
is coded as follows. Note that two operations occur:
- the string is encrypted using
EncryptData()
, - the string is written to the registry using
WriteNonEncBinary()
.
LONG CAESEncRegKey::WriteEncString(LPCTSTR pszData) const
{
LONG lResult = ERROR_SUCCESS;
BYTE* pcbEncryptedData = NULL;
DWORD dwEncryptedSize = 0;
lResult = EncryptData( reinterpret_cast<const BYTE*>(pszData),
( ::lstrlen( pszData ) + 1 ) * sizeof( TCHAR ),
&pcbEncryptedData, &dwEncryptedSize );
if( ERROR_SUCCESS != lResult ) { goto FINISHED; }
lResult = WriteNonEncBinary( pcbEncryptedData,
dwEncryptedSize );
FINISHED:
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, since writing data that has already been encrypted is same as writing non-encrypted data.
LONG CAESEncRegKey::WriteNonEncBinary(const BYTE *pcbData,
UINT nSize) const
{
LONG lResult = ERROR_SUCCESS;
HKEY hKey = NULL;
lResult = RegCreateKeyEx( _hKey, _szSubKey, 0, NULL,
REG_OPTION_NON_VOLATILE,
KEY_WRITE, NULL, &hKey, NULL );
if( ERROR_SUCCESS != lResult ) { goto FINISHED; }
lResult = RegSetValueEx(hKey, _szValueName, 0, REG_BINARY,
reinterpret_cast<const BYTE*>( pcbData ), nSize);
FINISHED:
if( NULL != hKey ) { RegCloseKey( hKey ); }
return lResult;
}
CAESEncRegKey Decryption
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 the AES::Decryption
object.
LONG CAESEncRegKey::ReadEncString(CString &szValue) const
{
LONG lResult = ERROR_SUCCESS;
DWORD dwSize = 0;
BYTE* pcbData = NULL;
DWORD dwDecryptedSize = 0;
BYTE* pcbDecryptedData = NULL;
lResult = ReadData( &pcbData, &dwSize );
if( ERROR_SUCCESS != lResult ) { goto FINISHED; }
lResult = DecryptData( pcbData, dwSize,
&pcbDecryptedData, &dwDecryptedSize );
if( ERROR_SUCCESS != lResult ) { goto FINISHED; }
szValue =
reinterpret_cast<const TCHAR*>( pcbDecryptedData );
FINISHED:
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 {
...
lResult = ReadData( &pcbRegistryData, &dwRegistrySize );
...
lResult =
DecryptData( pcbRegistryData, dwRegistrySize, &pcbDecryptedData,
&dwDecryptedSize );
...
if( *dwSize < dwDecryptedSize && NULL != pcbData ) {
lResult = ERROR_MORE_DATA;
*dwSize = dwDecryptedSize;
goto FINISHED;
}
if( *dwSize < dwDecryptedSize && NULL == pcbData ) {
lResult = ERROR_SUCCESS;
*dwSize = dwDecryptedSize;
goto FINISHED;
}
*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 show object construction:
CAESEncRegKey aesKey;
CAESEncRegKey aesKey(HKEY_LOCAL_MACHINE,
_T("\\Software\\Code Guru"), _T("Value Name"));
BYTE key[ AES::DEFAULT_KEYLENGTH ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE iv[ AES::BLOCKSIZE ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };
CAESEncRegKey aesKey( key, AES::DEFAULT_KEYLENGTH, iv, AES::BLOCKSIZE );
If using the default constructor, set the five data members before invoking a WriteXXX(...)
function, since the default constructor does not perform any state initialization.
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( "...", TRUE );
Reading data from the registry is simply the reverse operation:
CAESEncRegKey aesKey;
BYTE key[ AES::DEFAULT_KEYLENGTH ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE iv[ AES::BLOCKSIZE ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };
aesKey.SetKey( key, AES::DEFAULT_KEYLENGTH );
aesKey.SetIV ( iv, 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[ AES::DEFAULT_KEYLENGTH ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE iv[ AES::BLOCKSIZE ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };
...
aesKey.SetValueName( _T("Value 1") );
aesKey.ReadString( szData, TRUE );
aesKey.SetValueName( _T("Value 2") );
aesKey.ReadString( szData, TRUE );
aesKey.SetValueName( _T("Value 3") );
aesKey.ReadString( szData, TRUE );
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 );
}
}
History
- 12/03/2007
- Added Section Advanced Encryption Standard
- Added Crypto++ Encryption and Decryption
- Added Reference to Applied Crypto++: Block and Stream Ciphers'
- 11/16/2006
- Added Link to Crypto++ Compilation and Integration
- Removed Section Crypto++ and Visual C++ Compatibility'
- 10/10/2005 - Initial Release