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

原生 C++ 和 .NET 加密/自解密文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (14投票s)

2003 年 7 月 29 日

CPOL

3分钟阅读

viewsIcon

189748

downloadIcon

8163

根据密码和选定的算法一次性加密/自解密文件

wtlocapp

要求

WTL/ATL7 项目以 UNICODE 构建。如果您愿意,可以将解密器项目设置为非 Unicode。项目需要 Visual Studio 2003 或更高版本。WTL 项目需要 WTL,可以从 Microsoft 网站 下载。OpenSSL 项目需要 OpenSSL Crypto Lib

引言

这是一个简单的加密和自解密项目。它基于选定的算法和密码进行加密。它会输出一个自解密的可执行文件,该文件会提示输入密码来解密数据。对于“原生”版本,它创建自解压可执行文件的方法是嵌入资源,使用仅限于 NT 的 UpdateResource 函数。对于 .NET 版本,它从嵌入的 C# 源代码构建可执行文件,然后输出一个资源文件,并将其传递给编译器进行嵌入。

背景

需要注意的一些事项(以及其他许多事项)

  • 引用:“在使用分组密码的 CBC 模式时,您需要注意 CBC 滚转。[...] 平均而言,一个具有 X 位块大小的密码会在每 2^X/2 个块处发生两次密文值冲突。因此,重要的是不要用给定的密钥加密超过此量的数据” - SSL and TLS Designing and Building Secure Systems
  • 使用 SecureZeroMemory(它并非仅限 Win2k3)等助手来清除任何敏感数据,而不是使用 memset。在 .NET 中,至少使用 Array.Clear。请参考 Scrubbing Secrets in Memory (MSDN)。
  • 在完成对象使用后,请务必调用 SymmetricAlgorithm/HashAlgorithm 派生类和 CryptoStream 的 .Clear 方法。
  • .NET 提供了名为 PasswordDeriveBytes 的密码派生类,以防您需要从用户输入派生密钥。
  • OpenSSL 也提供了一个密码派生函数。

代码片段

ATL7 MS CryptoAPI 类 (无错误检查)

//these are ATL7 helper classes for dealing with MS Crypto API
CCryptProv prov;
//initialize the MS crypto provider
HRESULT hr = prov.Initialize([PROV_RSA_FULL|PROV_RSA_AES], NULL, 
                             [MS_ENHANCED_PROV|MS_ENH_RSA_AES_PROV]); 
//this check is needed in cases when crypto api was not used before
//on the machine then it will return NTE_BAD_KEYSET and we just need
//to initialize again and specify CRYPT_NEWKEYSET param
if(hr == NTE_BAD_KEYSET) 
  hr = prov.Initialize(dwProviderType, NULL, szProvider, 
                      CRYPT_NEWKEYSET); 

//we are deriving encryption key from user input
CCryptDerivedKey derKey; 
//SHA hash will be used to derive the key
CCryptSHAHash sha;
sha.Initialize(prov); 

//get password from user
TCHAR pPass1[MAX_PASS_LEN], pPass2[MAX_PASS_LEN];  
int nPass1 = ::GetWindowText(hWndPwd1, pPass1, MAX_PASS_LEN);  
int nPass2 = ::GetWindowText(hWndPwd2, pPass2, MAX_PASS_LEN); 

//add user's input to our hash
hr = sha.AddData((BYTE*)pPass1, nPass1*sizeof(TCHAR)); 

//clear the password buffers
SecureZeroMemory(pPass1, MAX_PASS_LEN*sizeof(TCHAR)); 
SecureZeroMemory(pPass2,MAX_PASS_LEN*sizeof(TCHAR)); 

//SHA is 20 bytes, so we can specify 20 right away
BYTE tmpHash[20];
DWORD dw= sizeof(tmpHash); 
//let's hash the password 101 times
//dictionary based attacks against the user password become harder
for(int i=0;i<101; ++i)
{ 
  hr = sha.GetValue(tmpHash, &dw);
  sha.Destroy();
  hr = sha.Initialize(prov);
  hr = sha.AddData(tmpHash, dw);
}
//clear the temp hash buffer
SecureZeroMemory(tmpHash, dw);

//initialize the algorithm which derives the key from our SHA hash
//and specify the algorithm to be used for encryption
hr = derKey.Initialize(prov,sha,[CALG_AES_256|CALG_3DES]); 

//largest is 16 for AES, 8 for 3DES
BYTE iv[16];
//generate cryptographically strong random bytes
hr = prov.GenRandom(16, iv);
//set above bytes as IV
hr = derKey.SetIV(iv);
//set mode to CBC
hr = derKey.SetMode(CRYPT_MODE_CBC);
//set padding
hr = derKey.SetPadding(PKCS5_PADDING);

//find out "dummy" embeded decryptor executable
HRSRC res =FindResource(NULL, MAKEINTRESOURCE(IDR_DUMMY), _T("dummy")); 
HGLOBAL hGlobal = LoadResource(NULL, res); 
int resLen = SizeofResource(NULL, res); 
void* buf = LockResource(hGlobal); 
//write out the decryptor executable
FILE* file = _tfopen((LPCTSTR)sFileName, _T("wb")); 
fwrite(buf, 1, resLen, file);
fclose(file);

//start the resources update for decryptor executable
BOOL bRet = FALSE;
HANDLE hUpdateRes = BeginUpdateResource(sFileName, FALSE);
//encrypt user's chosen file's data to be written to the decryptor resources
derKey.Encrypt(TRUE, (BYTE*)data.m_pData, &dwFileSize, dwFileSize + blocksize);

//get rid of the hash
sha.Destroy();

//write out algid, so decryptor will know with which alg to decrypt
UpdateResource(hUpdateRes, RT_RCDATA, _T("1"), 
               MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 
               (void*)(LPCTSTR)sAlgID, sAlgID.GetLength()*sizeof(TCHAR));
//write out filename, so decryptor will know what to name the file
//this can also be encrypted, but i didn't bother
UpdateResource(hUpdateRes, RT_RCDATA, _T("2"), 
               MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 
               dlgOpen.m_szFileTitle, 
               _tcslen(dlgOpen.m_szFileTitle)*sizeof(TCHAR));
//write out the encrypted data
UpdateResource(hUpdateRes, RT_RCDATA, _T("3"), 
             MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 
              data.m_pData, dwFileSize));
//write out random IV
UpdateResource(hUpdateRes, RT_RCDATA, _T("4"), 
             MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
             iv, sizeof(iv))
//finish resource update
EndUpdateResource(hUpdateRes, FALSE);

//Similar steps for decryption, just in reverse
//WTL app that uses OpenSSL crypto lib is very similar to this

.NET 应用 (无错误检查)

//Use .NET's helper class to derive encryption Key and IV from user's input
//iterate hash 100 times
PasswordDeriveBytes pdb = new PasswordDeriveBytes(txtPass1.Text, 
                          UnicodeEncoding.Unicode.GetBytes("somesalt"), 
                          "SHA512", 100);
SymmetricAlgorithm symalg=null;
//pick the algorithm to be used for encryption from user's choice
//and set Key and IV
//algid number will be written to the resources so that decryptor will
//know which alg to use for decryption
if(cmbAlg.Text == "Rijndael")
{
  symalg = new OpenCrypto.RijndaelOpenProvider();
  algid = "1";
  //just standard sizes
  Key = pdb.GetBytes(32);
  IV = pdb.GetBytes(16);
}
else if(cmbAlg.Text == "TripleDES")
{
  symalg = new OpenCrypto.TripleDESOpenProvider();
  algid = "2";
  //just standard sizes
  Key = pdb.GetBytes(24);
  IV = pdb.GetBytes(8);
}
else if(cmbAlg.Text == "Blowfish")
{
  symalg = new OpenCrypto.BlowfishOpenProvider();
  algid = "3";
  //just standard sizes
  Key = pdb.GetBytes(32);
  IV = pdb.GetBytes(8);
}
//etc ...

//open the user's chosen file
Stream inStream = dlgEncryptFile.OpenFile();
//create encryptor
ICryptoTransform encryptor = symalg.CreateEncryptor(Key, IV);
//clear the Key, encryptor now has it
Array.Clear(Key, 0, Key.Length);
//clear the IV, encryptor now has it
Array.Clear(IV, 0, IV.Length);
//use CryptoStream helper to write out the data for encryption
CryptoStream cryptStream = new CryptoStream(inStream, encryptor, 
                                            CryptoStreamMode.Read);

//preallocate the whole buffer + OutputBlockSize
//whole buffer is preallocated because AddResource later on takes only buffer
byte[] encrData = new byte[inStream.Length + encryptor.OutputBlockSize];
int readBlockSize = encryptor.OutputBlockSize * 1000;
int totalEncrBytes=0;
//loop through file and encrypt
for(int BytesRead=0; totalEncrBytes < encrData.Length; 
    totalEncrBytes += BytesRead)
{
  if(totalEncrBytes + readBlockSize > encrData.Length)
    readBlockSize = encrData.Length - totalEncrBytes;
  BytesRead = cryptStream.Read(encrData, totalEncrBytes, readBlockSize);
  if(BytesRead == 0)
    break;
}

//clear any sensitive resources
encryptor.Dispose();
cryptStream.Clear();
cryptStream.Close();
symalg.Clear();

//write out the resources into standard .net .resources file
ResourceWriter resWriter = new ResourceWriter(applicationPath + 
                                                "\\encrypted.resources");
resWriter.AddResource("1", algid);
//filename
resWriter.AddResource("2", fileTitle);
//total bytes encrypted
resWriter.AddResource("3", totalEncrBytes);
//encrypted file's data
resWriter.AddResource("4", encrData);
encrData = null;
inStream.Close();

//generate the resources
resWriter.Generate();
resWriter.Dispose();

//build the decryptor assembly dynamically using CSharpCodeProvider
BuildDecryptorAssembly();

//this is in finally{} block
//to ensure that .resources file will be deleted even if exception was thrown
if(File.Exists(applicationPath + "\\encrypted.resources"))
  File.Delete(applicationPath + "\\encrypted.resources");
    
private void BuildDecryptorAssembly()
{
//standard stuff to compile and build the assembly dynamically
  CSharpCodeProvider csprov = new CSharpCodeProvider();
  ICodeCompiler compiler = csprov.CreateCompiler();

//specify standard references, as well as the opencrypto.dll assembly
  CompilerParameters compilerparams = new CompilerParameters(new string[] 
                {"System.dll", "System.Windows.Forms.dll", "System.Drawing.dll", 
                  "opencrypto.dll"},
                  txtOutFile.Text, false);

  compilerparams.GenerateExecutable = true;
  
//specify our encrypted resource to be put into the new decryptor assembly
  compilerparams.CompilerOptions = "/target:winexe /resource:\""
                                   + applicationPath + 
                                   "\\encrypted.resources\",encrypted";
                                   
//compile assembly from source
  CompilerResults cr = compiler.CompileAssemblyFromSource(compilerparams, ...);
  
//this is just for debugging to see if there are any errors
  CompilerErrorCollection errs = cr.Errors;
  if(!errs.HasErrors)
    MessageBox.Show("Operation Successful");
  else
  {
    foreach(CompilerError err in errs)
      MessageBox.Show(err.ToString());
  }
}

//The pure .NET App's code is almost exactly the same

两个项目依赖于 OpenSSL

本文中的两个示例是基于 OpenSSL 加密库构建的,该库使用 Visual C++ 7 编译器可以正常构建。有关构建说明,请参阅 OpenSSL 库附带的 INSTALL.W32 文件。您可以构建静态/DLL 库版本的 Release/Debug 版本。OpenSSL 库实现的某些算法已获得专利。请参阅 OpenSSL 库的 README 文件,特别是专利部分。以下是摘录自 README 文件的一段话:

“各种公司在世界各地持有各种算法的各种软件专利。**您**有责任通过检查您所在国家/地区是否有相关专利来确保您对任何算法的使用是合法的。本文档包含我们已知或传闻存在的一些专利。这并非一个详尽的列表。

RSA Security 持有 RC5 算法的软件专利。如果您打算使用该密码,您必须联系 RSA Security 以获取许可条件。他们的网页是 http://www.rsasecurity.com/。RC4 是 RSA Security 的商标,因此使用此标签可能需要获得 RSA Security 的许可。

IDEA 算法由 Ascom 在奥地利、法国、德国、意大利、日本、荷兰、西班牙、瑞典、瑞士、英国和美国拥有专利。如果使用该算法,应联系他们;他们的网页是 http://www.ascom.ch/

要构建一个不包含专利算法(例如 mdc2、rc5、idea 等)的静态库,请为 Visual C++ WIN32 在 do_ms.bat 中定义:no-mdc2 no-rc5 no-idea。关于 RC* 算法可能存在的法律问题,您可以在 此处 阅读。

链接

历史

  • 2003 年 8 月 7 日 - 更新了 wtlcrypt 和文章内容
  • 2003 年 8 月 10 日 - 更新了 wtlcrypt 和文章内容
免责声明:本代码和信息按“原样”提供,不附带任何明示或暗示的保证,包括但不限于适销性和/或特定用途的适用性保证。
© . All rights reserved.