65.9K
CodeProject 正在变化。 阅读更多。
Home

AES加密注册表类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (30投票s)

2005 年 10 月 8 日

CPOL

7分钟阅读

viewsIcon

180567

downloadIcon

8953

本文介绍的另一个注册表类,它使用AES加密数据。

引言

Screenshot - image02.png

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 GuttmanCryptlib。鼓励读者修改本文中提供的程序以包含其他库。

背景

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_BINARYREG_DWORDREG_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()的代码如下。请注意,会发生两个操作:

  1. 使用EncryptData()加密字符串,
  2. 使用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(...)的功能,根据cbBuffercbSize参数返回ERROR_SUCCESSERROR_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日 - 初始发布
© . All rights reserved.