.NET 文件和字符串加密库






4.56/5 (17投票s)
2005 年 12 月 29 日
5分钟阅读

98111

2074
这是 .NET 框架加密类(HashAlgorithm、SymmetricAlgorithm)的 VB.NET 包装器,用于处理字符串和文件。
引言
尽管 CodeProject 和其他地方已经详细讨论了 .NET 框架的加密类,但我总觉得大多数示例要么过于简化,要么不够详尽。因此,我写了又一篇关于这个主题的文章。
大多数文章用几行代码展示了如何将字符串加密为字节数组。这很好,但是开发者拿到字节数组能做什么呢?通常这并没有什么用。
我也从未见过任何关于如何加密文件(无论大小)的良好示例。大多数示例会将文件内容读取到字节数组中,加密,然后写入新文件。对于 10 KB 的文件来说,这很棒,但你硬盘上的那个 700 MB 的电影怎么办?你应该尝试将 700 MB 的数据塞进你的 512 MB 内存中,加密,然后再写入文件吗?这可能不是个好主意。
你有没有尝试过用错误的密钥解密数据?你有没有注意到解密在得到一个非常描述性的“数据错误”之前就完成了?想象一下,将你加密的 700 MB 电影加载到你的 512 MB 内存中,等待你的电脑解密它,结果却因为你在密码中打错了字而得到一个“数据错误”!有了这样的错误,大多数人会认为文件已损坏并将其删除。
有了这个示例,我试图创建一个组件,你可以用它来满足你所有的加密需求,而无需修改。
使用此组件,您可以执行以下操作:
- 为字符串生成哈希值,并以字节数组或 Base64 字符串的形式接收输出,使用任何派生自
HashAlgorithm
的框架类; - 为文件生成哈希值,并以 Base64 字符串的形式接收输出,使用任何派生自
HashAlgorithm
的框架类; - 加密/解密字符串,并以 Base64 字符串的形式接收输出,使用任何派生自
SymmetricAlgorithm
的框架类; - 加密/解密文件,并将输出写入另一个文件,使用任何派生自
SymmetricAlgorithm
的框架类; - 使用随机数据安全地覆盖文件,从而有效地销毁它,使其无法恢复。
背景
有关加密的背景知识,请在 MSDN 上搜索“cryptography”或“encrypting data”。
使用代码
组件中的代码一点也不复杂,下载中包含的示例应用程序使用了组件的所有功能,因此我将在下面只讨论一些感兴趣的点。
关注点
- 文件加密、文件解密和文件哈希函数有一个
bufferSize
参数。这是函数一次处理的数据量,以 KB 为单位。在各种机器上进行测试时,256 KB 的缓冲区大小始终能提供最佳性能,因此这是默认的缓冲区大小。 - 让我们来看看文件哈希函数。
Public Function HashFileToBase64String(ByVal file As String, _ ByVal provider As HashAlgorithm, _ ByVal bufferSize As Integer) As String Dim fileStream As IO.FileStream Dim output As String Dim position As Long Dim length As Long Dim storage() As Byte Dim retStorage() As Byte Dim bytesRead As Integer Dim cea As CryptoEventArgs If bufferSize = 0 Then bufferSize = 256 If file Is Nothing OrElse file = "" Then Throw New ArgumentNullException("file", _ "'file' should not be Nothing" & _ " (null in C#) or String.Empty.") End If fileStream = New IO.FileStream(file, IO.FileMode.Open, _ IO.FileAccess.Read, IO.FileShare.None, _ bufferSize * 1024 - 1) If provider Is Nothing Then provider = New SHA512Managed ReDim storage(bufferSize * 1024 - 1) ReDim retStorage(bufferSize * 1024 - 1) cea = New CryptoEventArgs cea.StartTimeInternal = Now length = fileStream.Length cea.BytesTotalInternal = length While position < length bytesRead = fileStream.Read(storage, 0, _ storage.Length) position += bytesRead cea.BytesDoneInternal = position If Not position = length Then provider.TransformBlock(storage, 0, _ bytesRead, retStorage, 0) Else provider.TransformFinalBlock(storage, 0, bytesRead) End If cea.EndTimeInternal = Now RaiseEvent CryptoProgress(Me, cea) If cea.Cancel Then Exit While End While fileStream.Close() If Not cea.Cancel Then output = _ Convert.ToBase64String(provider.Hash) provider.Clear() cea.EndTimeInternal = Now RaiseEvent CryptoCompleted(Me, cea) cea.Dispose() Return output End Function
该函数包含一个私有变量
retStorage
,在While
循环中使用,用于从provider.TransformBlock
方法接收字节。写入此变量的字节在我们的函数中从未被使用,并且每次循环执行时都会被覆盖。但是,该变量是必需的,因为provider.TransformBlock
方法需要它,并且它必须具有正确的维度。要获得文件的计算哈希值,必须在调用provider.TransformFinalBlock
方法后评估provider.Hash
属性。 - 覆盖文件也带来了一些挑战。人们可能期望这是一项微不足道的工作,但数据的硬件缓存可能会导致严重问题。
问题是,如果你循环遍历一个文件并向文件中写入随机字节,操作系统会使用写缓存来稍微加快速度。如果你在向文件中写入随机字节后立即删除该文件,文件会在缓存中的数据写入磁盘之前被删除。文件实际上从未被覆盖!
幸运的是,古老的 API 可以为我们提供帮助。我们的组件包含一个名为
Files
的类,其中包含枚举、常量和函数声明,使我们能够打开一个文件,其中已为该文件禁用了写缓存。Friend Function OpenFileForSecureOverwrite(ByVal _ path As String) As FileStream If Not _openHandle.Equals(IntPtr.Zero) AndAlso _ Not _openHandle.ToInt32 = _ Me.INVALID_HANDLE_VALUE Then Me.CloseHandle(_openHandle) _openHandle = IntPtr.Zero End If _openHandle = Me.CreateFile(path, FileAccess.GENERIC_WRITE, _ FileShare.FILE_SHARE_READ Or FileShare.FILE_SHARE_WRITE, _ Nothing, CreationDisposition.OPEN_EXISTING, _ FlagsAndAttributes.FILE_FLAG_WRITE_THROUGH, Nothing) If _openHandle.ToInt32 = Me.INVALID_HANDLE_VALUE Then Return Nothing Else Return New FileStream(_openHandle, IO.FileAccess.ReadWrite) End If End Function
我们使用 Kernel32.dll 中的
CreateFile
来打开文件并返回打开文件的句柄。然后,我们将句柄传递给FileStream
的构造函数,以便我们可以在 .NET 中继续处理该文件。所有这些中最重要的部分是传递给CreateFile
作为参数之一的FlagsAndAttributes.FILE_FLAG_WRITE_THROUGH
枚举值。此值会禁用打开文件的写缓存。这里明显的缺点是性能会下降约 50%。另一件事是,某些硬件不支持禁用写缓存的功能。
- 使用我们的组件加密的所有文件和字符串都包含一个 256 字节的头部。
前 127 字节包含用于加密数据的密码的哈希值。在解密过程中使用此哈希值,以提前确定密码是否正确,从而避免了恼人的“数据错误”。
第二个 127 字节用于存储用于加密文件的 IV。一些 Microsoft 文档说,IV 不需要保密(请参阅 Microsoft 的 Cryptography Overview 中“Secret-Key Encryption”部分的底部),而有些则不然。关键是,没有正确的 IV,你就无法正确解密数据,因此我们将其保存在这里。
最后两个字节用于存储一个值,该值指示加密使用了哪个 .NET
SymmetricAlgorithm
派生类。我们组件的解密函数没有provider
参数。提供程序由保存在头中的值确定。 - 最后,我们的组件包含两个事件,它们公开
CryptoEventArgs
类。这些事件仅在文件操作时触发,该类公开与操作进度相关的属性。该类的唯一可写属性是Cancel
。如果希望取消操作,请将此属性设置为True
。下载中包含的示例应用程序使用了此功能。
历史
- 2005/12/28:首次发布!请评价并留下评论!