一个简单的 MFC 类, 用于使用 Crypto API 加密数据
一篇演示如何使用 Crypto API 创建一个简单的 MFC 类,
引言
本文介绍如何使用 Microsoft Cryptography 创建一个简单的 MFC 类来加密和解密数据。
Microsoft 的 Cryptography API 非常强大,但使用起来有点笨拙。如果您需要快速地为您的软件添加基于密码的加密安全功能,那么这个小类将能够让您非常快速地加密和解密多种不同类型的数据。
使用代码
在我深入讲解代码工作原理之前,这里有一个简短的使用示例。首先,请确保在您的项目中包含 Crypto.cpp 和 Crypto.h。如果您要在多个模块中使用 Cryptography 代码,请将其包含在 stdafx.h 中。否则,只需将其包含在您将要使用的模块中。要进行任何加密操作,首先必须创建一个对象,然后从中派生一个密钥。目前,派生密钥的唯一方法是基于密码。
// Create the Crypto object, this initializes all of
// the underlying handles etc.
CCrypto crypto;
// Ask the user for a password.
if(somePasswordDialog.DoModal() == IDOK)
{
// Set the password for the crypto object.
cryto.DeriveKey(somePasswordDialog.m_strPassword);
// Now the crypto object is ready to use
// ... some code using encryption and decryption ...
}
因此,设置 Crypto
对象以供使用非常简单。如果您想知道如何确保用户已指定正确的密码,请继续阅读文章后面的内容。那里会对此进行讨论。
使用此对象加密数据时,始终会传递一个对 CByteArray
的引用。加密后的字节将存储在此处。这可能看起来有点笨拙,但最终事实证明这是最快速、最简单的方法。我们不能将加密字符串的结果存储在另一个字符串中,因为加密后的数据可能包含零。生成的字符串将是格式错误的。
尽管如此,为什么是字节数组?原因是该类不想知道您想将数据存储在哪里。它可能是一个文件;它可能是剪贴板;它可能是注册表。它只知道您之后会将加密数据存储在某个地方,因此字节数组是将它传递过来传递过去的最方便的方法。
同样,在某个阶段,您将从文件、注册表、剪贴板、套接字或任何地方加载加密数据。解密函数将以字节数组的形式接收该数据。总的来说,这比编写一个可以直接加密到文件、套接字或其他任何东西的类要好得多。这个类所做的只是加密或解密到和从字节数组的数据。存储由您决定。
那么,如何加密数据呢?这里有一个例子。
// I assume that you have a crypto object here, that has been
// constructed and has a key derived for it!
CCrypto crypto;
// Create a byte array, we will use this later on to store data.
CByteArray arBytes;
// To start off with, lets encrypt a string.
CString str = _T("What the hell am I supposed to write here?");
if(crypto.Encrypt(str, arBytes) == true)
{
// Store the byte array in a file, the registry, whatever.
}
// Now I want to encrypt my applications document object.
CDocument* pDoc = GetDocument();
if(crypto.Encrypt(*pDoc, arBytes) == true)
{
// Store the byte array in a file, the registry, whatever.
}
// Finally, I want to encrypt an array of strings.
CStringArray arStrings;
arStrings.Add(_T("String One"));
arStrings.Add(_T("String Two"));
arStrings.Add(_T("String Three"));
if(crypto.Encrypt(arStrings, arBytes) == true)
{
// Store the byte array in a file, the registry, whatever.
}
再简单不过了。如果您真的很头疼,想加密数据并将其转储到文件中,只需几行代码即可打开一个 CFile
对象并调用 Write 来写入字节数组的内容。解密呢?看看下面的代码。
// I assume that you have a crypto object here, that has been
// constructed and has a key derived for it!
CCrypto crypto;
// Create a byte array, we will use this later on to store data.
CByteArray arBytes;
// ...load the contents of a file, registry key, whatever contains your
// data to the byte array first!...
CString strDecrypred;
if(crypto.Decrypt(arBytes, strDecrypred) == true)
{
cout << strDecrypted.GetString();
}
// Now I want to decrypt my applications document object.
CDocument* pDoc = GetDocument();
if(crypto.Decrypt(arBytes, *pDoc) == true)
{
}
// Finally, I want to decrypt an array of strings.
CStringArray arStrings;
if(crypto.Decrypt(arBytes, arStrings) == true)
{
}
简单方便。您需要做的所有工作就是用您从文件或其他地方检索到的加密数据填充 arBytes
变量。这两个代码示例展示了加密对象的所有函数。
我可以加密什么?
我本来可以做得更绝,提供加密 int
、double
、std::string
、CFile
对象等的函数。但是,我故意限制了您可以加密和解密的对象类型为两种,这涵盖了我们需要的所有内容。
CStrings
- 这是一个非常常见的对象。如果您想加密任何类型的字符串,请将其转换为CString
。我更喜欢std::string
,但这是一个 MFC 类,而且CString
在 Unicode 环境中也能很好地编译。- 可序列化的
CObjects
- 这涵盖了文档对象、容器以及所有其他内容。
在 MFC 中,如果您希望某个对象能够被序列化为一组字节,那么您需要使其可序列化。任何可以序列化为字节流的内容都可以被加密和解密。这非常有用,因为您可以直接加密应用程序文档对象、数组、列表以及任何被设计为可序列化的内容!每个人都知道如何使 MFC 类可序列化;这非常容易做到。因此,如果您有一个需要加密的复杂数据类型,请将其声明为 CObject
,使用 DECLARE_SERIAL
和 IMPLEMENT_SERIAL
。然后编写 serialize 函数,您就完成了。您可以随心所欲地加密和解密。
那 unsigned char[] 呢?
如果您想加密一个字节缓冲区的内容或类似的东西,请将缓冲区复制到 CByteArray
。这是可序列化的,因此您可以对其进行加密。我不想编写一个接受指向随机内存区域的指针并假设已传递正确长度的函数。如果您正在处理原始内存区域,请使用 CByteArray
。这将使您的代码更安全、更易读、更高效。
太好了。谢谢。那 Win32 呢?
我本来很想让这个类只能加密 std::string
或 std::vector<unsigned char>
,但利用 MFC 的序列化机制让我免费获得了如此多的功能。只需几页的代码,我就可以加密和解密任何我愿意为其编写 serialize 函数的内容。它如此快速和简单,我无法抗拒。对不起,Win32。对不起,STL。
这究竟意味着什么,Basil?它是如何运作的?
快速浏览一下代码。只有两个文件,只有两个重要函数。好的,这里是概要(实际上不是概要,而是全部内容)。
- 构造 - 初始化所有加密句柄等。如果失败,您的对象将毫无用处。我通常讨厌将这种初始化放在构造函数中,但有什么关系呢。如果 Crypto API 的初始化无法完成,您的软件就会完蛋。重新安装 Platform SDK。
- 析构 - 嗯,释放上面的所有内容。
- 加密 - 好的,这更有意思了。对于可序列化的对象:从 Crypto 对象拥有的
CMemFile
创建一个CArchive
,该CMemFile
用作其操作的缓冲区。使用Serialize
将可序列化对象(哇!)序列化到存档。调用InternalEncrypt
调用 API 函数,将内存文件的内容加密到字节数组。对于字符串:从 Crypto 对象拥有的CMemFile
创建一个CArchive
,该CMemFile
用作其操作的缓冲区。使用CArchive::operator <<
序列化字符串。调用InternalEncrypt
调用 API 函数,将内存文件的内容加密到字节数组。 - 解密 - 与上述非常相似。对于可序列化的对象:使用
InternalDecrypt
将字节数组解密到内存文件。从内存文件创建CArchive
。在对象上调用Serialize
,传入存档。全部完成。容易。对于字符串:使用InternalDecrypt
将字节数组解密到内存文件。从内存文件创建CArchive
。调用字符串上的CArchive::operator <<
。全部完成。容易。
所以,这一切都很简单。我使用 CMemFile
作为所有加密和解密操作的临时存储区,这样我就可以轻松地附加一个存档。此外,总有一天我可能想加密 12 GB 的数据,那样的话我会使用磁盘文件。
代码不会做什么
我已将此类尽可能简化,因此有一些事情它不会做,尽管我计划添加某些功能。此类不会
- 直接序列化 STL 类型或内置类型。抱歉,您必须将它们转换为字节数组或可序列化对象。有一个将对象转换为字节数组的机制,即序列化,所以请使用它。
- 直接序列化到文件。当然,您可以一键加密或解密文件,但如果您正在加密或解密内存中的数据,则需要您自己打开文件或保存它。
- 构造失败时不会有礼貌。如果 Crypto API 向我抛出错误,
Encrypt
和Decrypt
将返回 false。这是到目前为止错误报告的最好水平。 - 处理非密码加密机制。这只是提供另一个类似
DeriveKey
的函数,该函数接受一个密钥文件或其他内容,但目前我只支持密码保护。添加更多功能非常容易,而且不会改变代码的内部工作方式。
关注点
看看 InternalEncrypt
或 InternalDecrypt
。在大约十五行的空间内,MFC 使用以下方式指定了缓冲区长度:
int
UINT
DWORD
INT_PTR
DWORD_PTR
ULONGLONG
真是个笑话!STL 在决定保持一致并在所有地方使用 size_t
时做得对。在我所有非 MFC 的代码中,我在指定长度、大小、数组索引等的地方都使用 size_t
。代码中有很多 static_cast
,因为 MFC 对大小的使用不一致。
什么是正确的密码?
如果 Decrypt
返回 false,您可能使用了错误的密码。如果您想能够进行检查,请执行以下操作:
- 当用户第一次指定密码时,使用由密码生成的密钥加密该密码的值,即调用
DeriveKey
然后调用Encrypt
。将加密后的密码保存在注册表/数据文件/其他地方。 - 下次用户指定密码时,尝试解密他们的原始密码。如果解密成功并且解密后的密码与用户提供给您的密码匹配,那么他们就指定了正确的密码。
据我所知,这比加密一个常量字符串并检查解密后是否得到相同字符串更安全。这是因为如果有人发现了您用于检查的字符串,他们就可以编写一个程序来生成密码,检查生成的密码是否能正确解密检查字符串,然后破解您的应用程序(如果它有效)。所以,请加密密码本身。
更新和更多信息
如果我更新代码,我会在 我的网站 上进行更新,同时也会在此处更新,所以请查看那里获取更新。如果您有评论或建议,请随时 给我发邮件。
免费内容
我的网站 包含您可能喜欢的免费软件。如果您喜欢它或这段代码,请 给我买杯啤酒,因为我穷困潦倒。真的,真的非常穷。
历史
- 2007 年 7 月 3 日 -- 发布原始版本