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






4.69/5 (14投票s)
根据密码和选定的算法一次性加密/自解密文件
- 下载加密器/自解密器 (WTL7/ATL7 使用 MS CryptoAPI) - 25.8 KB
- 下载加密器/自解密器 (.NET) - 11.7 KB
- 下载加密器/自解密器 (WTL7/ATL7 使用 OpenSSL Crypto) - 27.1 KB
- 下载加密器/自解密器 (.NET 使用 OpenSSL Crypto) - 35.0 KB

要求
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* 算法可能存在的法律问题,您可以在 此处 阅读。
链接
- Handbook of Applied Cryptography (PDF)
- MS CryptoAPI
- OpenSSL
- ATL7 加密参考
- CryptAcquireContext() 的使用和故障排除
- RC4
- 关于流密码和 RC4 的讲义
- David Wagner 的加密帖子
历史
- 2003 年 8 月 7 日 - 更新了
wtlcrypt
和文章内容 - 2003 年 8 月 10 日 - 更新了
wtlcrypt
和文章内容