AES加密注册表类






4.86/5 (30投票s)
本文介绍的另一个注册表类,它使用AES加密数据。
引言

CAESEncRegKey
类解决了安全地将数据保存到注册表中的偶尔出现的问题。本文介绍的另一个注册表类,它使用AES加密数据。这里介绍的类使用了AES或高级加密标准的CBC模式。
目前,NIST已经批准了三种对称加密算法供联邦处理使用:AES、Triple DES和Skipjack。AES在FIPS-197中有规定。AES最初被称为Rijndeal(发音为“Rhine dahl”),是Joan Daemen和Vincent Rijmen的工作——因此得名Rijndael。AES是一种块密码,接受可变长度的密钥。
DES算法于1976年由FIPS-46推出。1988年,DES进行了第一次修订,产生了FIPS-46-1。FIPS-46-2于1993年发布。Triple DES (TDES) 于1998年在FIPS-46-3中获得批准。然后,在2005年5月,DES和Triple DES被撤销标准。由于在联邦领域中的流行,Triple DES在NIST SP800-67中获得了一个暂缓期,直到2030年。
最后,Skipjack在FIPS-185中有规定。FIPS-185规定了密钥托管加密标准。FIPS-185是允许执法部门或司法机构拦截的加密电信流解密的框架。
Crypto++
底层加密库是Wei Dai的Crypto++。有关使用Crypto++的更多信息,请参阅将Crypto++集成到Microsoft Visual C++环境中。如果您不熟悉该库,请花些时间熟悉它。还存在其他加密库,例如Peter Guttman的Cryptlib。鼓励读者修改本文中提供的程序以包含其他库。
背景
Windows在注册表中维护一个名为SAM(安全账户管理器)的安全区域。用户、管理员和程序员通常不允许直接访问注册表的此区域。必须使用LSA系列等API函数;或使用域用户管理器或Active Directory用户和计算机等工具。SAM包含诸如缓存的用户登录信息和本地账户等信息。
为了允许程序员安全地将数据保存到注册表(而不是SAM),程序员可以使用CAESEncRegKey
。但是,在使用注册表时需要注意一些限制。对于本文的目的来说,最重要的是将二进制数据(值类型REG_BINARY
)的大小限制在2048字节或以下。请参阅MSDN中的Microsoft 注册表元素大小限制。
使用内置的Windows计划程序(at.exe)从命令行,我们计划启动regedit.exe,以便我们可以探索SAM树。由于计划程序在NT_AUTHORITY下运行,regedit将以内置的系统权限和特权启动。
下载次数
本文有四个下载项。GUI演示是一个发布版本的演示,它演示了CAESEncRegKey
类。CLI演示是使用Crypto++库的AES概念验证。它仅仅演示了Crypto++的AES加密/解密过程。
密钥和IV生成器程序使用Crypto++的AutoSeededRandomPool
PRNG来创建密钥和IV向量的伪随机值。有关随机数生成器使用指南,请参阅伪随机数生成器调查。
最后的示例演示了加密注册表类。
高级加密标准
AES于2001年11月由NIST标准化。AES是一种128位块密码,接受128、192和256位的密钥长度。有七种操作模式被批准用于AES。操作模式指定一个阶段(例如阶段i)的输出如何用作后续阶段(例如i+1)的输入。由于所有模式(ECB除外)都在反馈机制中使用,因此模式需要一个初始化向量:在迭代0时,没有来自迭代-1的反馈。
FIPS-81和SP800-38A提供了五种保密性模式:ECB、CBC、CFB、OFB和CTR。当我们提到加密时,保密性通常是我们想到的。认证由SP800-38A中指定的CMAC提供。当需要同时进行保密性和认证时(CCM有时被称为CBC-MAC),可以使用SP800-38C的CCM模式。
最后,ANSI X9.52规定了七种模式。四种模式等同于FIPS-81:ECB、CBC、CFB和OFB模式。其余三种模式是CBC、CFB和OFB模式的变体。
Crypto++加密和解密过程
请参阅应用Crypto++:块密码,了解该库对对称加密算法实现的完整讨论。本文深入探讨了StreamTransformationFilters、TransformationFilters和操作模式。
通过使用附加到Crypto++加密或解密对象的过滤器,我们可以避免与AES等块密码相关的缓冲和填充问题。
// Key Setup
byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ];
byte iv[ CryptoPP::AES::BLOCKSIZE ];
::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
::memset( iv, 0x01, CryptoPP::AES::BLOCKSIZE );
// Message M
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.'";
// Encryptor
CryptoPP::CFB_Mode< CryptoPP::AES >::Encryption
Encryptor( key, sizeof(key), iv);
// Encryption
CryptoPP::StringSource( PlainText, true,
new CryptoPP::StreamTransformationFilter(
Encryptor,
new CryptoPP::StringSink( CipherText )
) // StreamTransformationFilter
); // StringSource
// Decryptor
CryptoPP::ECB_Mode< CryptoPP::AES >::Decryption
Decryptor( key, sizeof(key),iv );
// Decryption
CryptoPP::StringSource( CipherText, true,
new CryptoPP::StreamTransformationFilter(
Decryptor,
new CryptoPP::StringSink( RecoveredText )
) // StreamTransformationFilter
); // StringSource
// RecoveredText is ready for use
CAESEncRegKey类
CAESEncRegKey
包含用于读取和写入加密和未加密数据的成员函数。该类在标准API的基础上增加了一个额外的参数,允许我们指定是否要加密数据。为了对现有源代码产生最小的影响,布尔值默认为false。
支持的注册表值类型有REG_BINARY
、REG_DWORD
和REG_SZ
。下面的内容将讨论加密字符串的写入和读取操作。该类包含用于以下内容的有状态表示的数据成员。
- AES密钥
- AES IV
- HKEY
- 子键
- 值名称
通常,大多数值一旦设置就不会改变。例外是ValueName,它在读取和写入多个数据时会改变。AES Key和AES IV是struct
,以简化操作。SubKey和ValueName是CStrings
。遵循Microsoft注册表API的精神,所有函数都返回LONG
类型。
CAESEncRegKey加密
下面是WriteString()
函数。根据需要,它将调用WriteEncString()
或WriteNonEncString()
。
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()
的行为与其他注册表类相同——它将字符串写入注册表。使用RegCreateKeyEx()
打开用于写入的键,因为RegCreateKeyEx()
会创建键或打开现有键。WriteEncString()
的代码如下。请注意,会发生两个操作:
- 使用
EncryptData()
加密字符串, - 使用
WriteNonEncBinary()
将字符串写入注册表。
LONG CAESEncRegKey::WriteEncString(LPCTSTR pszData) const
{
LONG lResult = ERROR_SUCCESS;
// Returned from EncryptData()
BYTE* pcbEncryptedData = NULL;
DWORD dwEncryptedSize = 0;
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()
执行注册表写入操作,就像它只是被调用来写入未加密的二进制数据一样。WriteNonEncBinary()
会被频繁调用,因为写入已加密的数据与写入未加密的数据相同。
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;
}
CAESEncRegKey解密
ReadEncString()
函数调用ReadData()
,该函数返回加密字符串的二进制数据和大小。由ReadEncString()
负责释放它。另外请注意,ReadData()
使用RegOpenKeyEx()
打开键——如果键不存在,则不会创建。
一旦获得加密数据,就会调用DecryptData()
。同样,DecryptData()
函数只是使用AES::Decryption
对象解密数据。
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;
}
最后,在读取数据时,CAESEncRegKey
模仿RegQueryValueEx(...)
的功能,根据cbBuffer
和cbSize
参数返回ERROR_SUCCESS
或ERROR_MORE_DATA
。有关详细说明,请参阅MSDN中的RegQueryValueEx。下面是ReadEncBinary(...)
的一个示例。请注意,数据必须先从注册表中读取,然后才能确定缓冲区大小。
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;
}
使用CAESEncRegKey
CAESEncRegKey
不仅提供读写注册表值类型的函数,还提供用于更改注册表键对象的变异器和访问器。如前所述,两个辅助结构用于管理AES密钥和AES IV。这些结构在aeshelper.h中声明和定义。
以下三个示例展示了对象构造
// 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[ AES::DEFAULT_KEYLENGTH ] = { 0x00, 0x01, ... , 0x0E, 0x0F };
BYTE iv[ AES::BLOCKSIZE ] = { 0x0F, 0x0E, ... , 0x01, 0x00 };
CAESEncRegKey aesKey( key, AES::DEFAULT_KEYLENGTH, iv, AES::BLOCKSIZE );
如果使用默认构造函数,请在调用WriteXXX()
函数之前设置五个数据成员,因为默认构造函数不执行任何状态初始化。
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 );
从注册表中读取数据只是一个反向操作
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 );
读取多个键值可以通过以下方式完成
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 );
// 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
最后,驱动程序中的代码验证了加密二进制数据的读写
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 );
}
}
历史
- 12/03/2007
- 添加了高级加密标准部分
- 添加了Crypto++加密和解密
- 添加了对“应用Crypto++:块和流密码”的引用
- 11/16/2006
- 添加了Crypto++编译和集成链接
- 删除了“Crypto++和Visual C++兼容性”部分
- 2005年10月10日 - 初始发布