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

一个简单、便携、基于 Rinjdael (AES) 的流密码类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.45/5 (15投票s)

2005年6月12日

CDDL

6分钟阅读

viewsIcon

141567

downloadIcon

3387

一个具有内置 MD5 字符串和文件哈希的文件加密/解密类。

引言

本文是对初版代码的重大更新,很大程度上基于读者的建议。

我在CalcMD5FromFile()中删除了 Win32 调用,并用兼容 UNIX 的fopen(), fread()等替换了它们。我认为该代码现在已完全可移植。

此更新用 Rinjdael 块密码实现替换了之前的加密方案 - 它包装了 Szymon Stefanek 的一个类。我使用的 Rinjdael 实现是一个 128 位 CBC 块密码,带有 256 位密钥。DCipher类将其包装在一个流密码类中,该类以数据块加密文件。您可以通过调用SetChunkSize(int numKB)来设置块大小,然后再进行加密。如果您不设置大小,它将使用默认的 65 kb 块大小。这里有几点需要注意:

  1. 块越大,加密越安全——理想情况下,您希望将文件加密为单个数据块,以充分利用 Rijndael 的块链接功能,
  2. 但是,您必须确保有足够的可用内存,同时要记住必须分配输入和输出内存块,所以要使用 100K 数据块,您必须有 200K + 16 字节的内存可用。虽然堆可以利用虚拟内存,但DCipher会验证块大小与可用物理内存的匹配度,以防止敏感数据写入磁盘。
  3. 最后,您必须使用与加密时相同的块大小进行解密,否则数据将被损坏。

我还更改了接口:类方法已修改为接受纯文本密码。然后,该类会将纯文本密码转换为 256 位十六进制 Rijndael 会话密钥。

此外,您不再在构造函数中初始化会话;所有内容都直接传递给函数。要加密/解密,只需传递完整的文件路径、密码和一个 19 字节的整数数组。请参阅下面的摘要

////////////////////////////////////
// Implementation
////////////////////////////////////

int EncryptFile(const char *src, const char* pwd, 
                                      int head[19]);
int DecryptFile(const char *src, const char* pwd, 
                                      int head[19]);

char* GetSignature(const char *file);
void SetChunkSize(int numKB);

char* CalcMD5FromString(const char *s8_Input);
char* CalcMD5FromFile  (const char *s8_Path);

整数数组允许您为多个应用程序提供唯一的签名,这样如果您使用的是应用程序生成的密钥,就不会有一个应用程序损坏另一个应用程序的文件。它还允许您为单个文件提供唯一的签名(例如时间戳 - 请参阅演示应用程序代码中的示例),如果您想使用文件哈希来检查修改。

添加了GetSignature()方法以协助处理此事。您可以将文件签名及其对应的纯文本文件哈希和加密文件哈希存储在数据库中。然后,您可以将文件签名作为查找键,或者您可以将文件哈希用作查找键。无论哪种方式,您都可以验证文件在加密后是否被修改(如果被修改,它将无法正确解密),然后您可以恢复备份或通知用户数据文件无效。您也可以在解密后比较纯文本文件哈希,以确保文件已正确解密。

此外,Rijndael 要求块大小必须是 16 的倍数。如果需要,会添加填充字符以满足此要求。DCipher类会将填充值记录在签名中的最后两个字节(总共 25 字节的标签,因为DCipher还使用内部 4 字节签名来检查文件状态)。然后在解密时,DCipher能够读取此值并在解密过程中删除这些字节,因此解密后的文件与原始文件完全匹配。

使用代码

使用此代码很简单。演示下载演示了如何在 MFC 应用程序中使用此类。

要使用该代码,请将DCipher.hDCipher.cpprijndael.hrijndael.cpp复制到您的项目文件夹,然后使用 Project/Add To Project/Files 将它们添加到您的项目中。然后在将要调用这些函数的类文件中(通常是视图类或对话框类),在文件顶部与您其他的include语句一起添加#include "DCipher.h"

我将描述如何在 MFC 应用程序中使用它,因为你们这些非 MFC 人员应该已经理解了const char*

EncryptFile()DecryptFile()方法要求您传递完整的文件名(包括路径)以及密码和签名数组。现在,通常情况下,您会在全局声明您的签名数组,或者在函数内部生成它,并从CFileDialogCDocument类的GetPathname(),或者从AppClassm_lpCmdLine变量(如果文件是通过双击或命令行请求打开的)中检索您的文件路径,或者从HDROPINFO结构(如果是拖放操作)中检索。无论如何,为了简单和可读性,我将在本例中将其硬编码。那么,您的代码,用于加密文件,将如下所示:

int sig1[] =  {1,1,0,0,4,8,6,0,2,9,4,1,1,0,5,6,0,7,3};
CString szFilename = "c:\\somepath\\somefile.ext";    
CString szPass = "SomePassKey";

DCipher dc;
int nResult = dc.EncryptFile((LPCTSTR)szFilename, 
                             (LPCTSTR)szPass, sig1);
         
if(nResult < 1)
   // Encryption was successful
else
   // Encryption failed - see defines in DCipher.h 
   // for error codes

解密文件将使用完全相同的代码,只是您将调用DecryptFile()而不是EncryptFile()

CalcMD5FromString()从字符串输入返回一个混合大小写的哈希 - 相当不言自明。

CalcMD5FromFile()返回一个文件的 32 字节大写哈希。这不仅仅是对文件名字符串值的哈希 - 它是对文件内容的哈希,并用于检查文件是否被修改。因此,您**不**想将其用作加密密钥,因为您将永远无法解密文件,除非密钥被存储在某个地方(我认为这是一个坏主意)。

例如,在 MFC 应用程序中调用这些方法如下所示:

//Get hash of file
DCipher dc;
CString szHashValue = 
    dc.CalcMD5FromFile((LPCTSTR)szFullFilePath);
                 
//Get hash from string
DCipher dc;
CString szHashValue = 
    dc.CalcMD5FromString((LPCTSTR)szTextString);

就这样。它是一个极其易于使用的类。它适用于**任何**类型的文件,包括图像。我在 Win2000 和 XP(始终使用 VC6 编译)上测试过此类 - 并且我测试过大小从 1K 到 300K 的文件。我没有测试过非常大的文件(例如 2GB) - 但它在我测试过的文件上速度非常快。

我不知道有任何编译器问题。如果您发现任何需要纠正、改进的地方,或者您对代码进行了重大修改,请将这些信息传达给我,以便我将其包含在将来的更新中,我将很乐意为您的贡献署名。

免责声明

我应该说明,MD5 代码不是我编写的。我在一个CodeProject 文章的评论部分找到了它,由一个别名为 Elmue 的用户发布。由于他没有声称对此代码拥有作者身份,我认为它可能最初来自其他来源。如果有人知道其合法作者是谁,我很乐意在此处注明。

我只做了小的修改

  1. 最初,文件哈希和字符串哈希方法使用相同的字符串转换函数,该函数返回一个大写十六进制字符串。我为字符串哈希方法添加了一个单独的返回函数,该函数返回一个混合大小写的十六进制字符串 - 这使得加密密钥更强大,并且
  2. 用与 UNIX 兼容的函数替换了 Win32 文件读取函数,以提高可移植性。

历史

  • 2005.Jun.12

    首次在 CodeProject 发布。

  • 2005.Jun.19

    更改了接口,添加了用户密码,添加了 Rijndael 算法。

  • 2005.Jun.21

    删除了 MD5 中的 Win32 调用 - 希望现在完全可移植。

  • 2005.July.5

    添加了GetSignature()SetChunkSize()和内存验证。

© . All rights reserved.