.NET cryptography library for files and strings






4.56/5 (17 votes)
Dec 29, 2005
5 min read

97534

2074
This is a VB.NET wrapper for the .NET framework cryptography classes (HashAlgorithm, SymmetricAlgorithm) for working with strings and files.
Introduction
Although the cryptography classes of the .NET framework have been discussed at length on CodeProject and elsewhere, I can not help but think that most examples are either over simplified or just not thorough enough. And hence I have written yet another article on the topic.
Most articles show in a few lines of code how to encrypt a string into a byte array. That's just great, but what is a developer to do with just a byte array? Often that is just not useful.
I have also never seen any good examples of how to encrypt files, small or large. Most examples read the contents of the file into a byte array, encrypt it, and write it out to a new file. That's just fabulous for 10 KB files, but what about that 700 MB movie you have on your hard drive? Should you try to cram that 700 MB into your 512 MB RAM, encrypt it, and then write it out to a file? Probably, not such a good idea.
Have you ever tried to decrypt data with an incorrect key? Did you notice that the decryption completes before you get a very descriptive 'Bad Data' error? Imagine loading your encrypted 700 MB movie into your 512 MB RAM, waiting for your poor PC to decrypt the thing only to get a 'Bad Data' error because you made a typo in your password. With an error like that, most people will think the file was corrupted and get rid of it.
With this example, I have tried to create a component that you can use for all your encryption needs with no modifications required.
With this component, you can do the following:
- Generate hash values for strings and receive the output as a byte array or Base64 string, using any of the framework classes that derive from
HashAlgorithm
; - Generate hash values for files and receive the output as a Base64 string, using any of the framework classes that derive from
HashAlgorithm
; - Encrypt/decrypt strings and receive the output as a Base64 string, using any of the framework classes that derive from
SymmetricAlgorithm
; - Encrypt/decrypt files and write the output to another file, using any of the framework classes that derive from
SymmetricAlgorithm
; - Securely overwrite a file with random data, effectively destroying it and making it unrecoverable.
Background
For background on cryptography, do a search for 'cryptography' or 'encrypting data' at MSDN.
Using the code
The code in the component is not rocket science at all and the sample app included in the download uses all the functionality of the component, I will therefore only discuss some points of interest below.
Points of Interest
- The file encryption, file decryption, and file hashing functions have a
bufferSize
parameter. This is the amount of data the functions will work with at any given time, in KB. While conducting tests on various machines, a buffer size of 256 KB has consistently given the best performance, and hence that is the default buffer size. - Let's take a look at the file hashing function:
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
The function contains a private variable,
retStorage
, which is used in theWhile
loop to receive bytes from theprovider.TransformBlock
method. The bytes written to this variable are never used anywhere in our function and are overwritten every time the loop executes. However, the variable is required as theprovider.TransformBlock
method expects it, and it has to have the correct dimension as well. To get the calculated hash value of the file, one has to evaluate theprovider.Hash
property after theprovider.TransformFinalBlock
method has been called. - Overwriting files also pose a bit of a challenge. One might expect it to be a trivial task but hardware caching of data can cause a serious problem.
The problem is that if you loop through a file and write random bytes to the file, the OS uses write caching to speed things up a bit. If you immediately delete the file after writing your random bytes to it, the file gets deleted before the data in the cache is written to disk. The file never actually gets overwritten!
Luckily, the good old API can come to our rescue. Our component here contains a class called
Files
which contains the enumerations, constants, and function declarations that will enable us to open a file where write caching has been disabled for the file.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
We use
CreateFile
found in Kernel32.dll to open the file and return a handle to the open file. We then pass the handle to the constructor ofFileStream
to allow us to continue working with the file in .NET. The important bit in all this is theFlagsAndAttributes.FILE_FLAG_WRITE_THROUGH
enumeration value that is passed toCreateFile
as one of the parameters. This value disables the write caching for the open file.The obvious drawback here is that performance takes about a 50% hit. Another thing is that some hardware do not support the ability to disable write caching.
- All files and strings that get encrypted with our component contain a 256 byte header.
The first 127 bytes contain a hash value of the password used to encrypt the data. This hash is used during decryption to determine upfront if the password is correct, thus avoiding that annoying 'Bad Data' error.
The second 127 bytes is used to store the IV used for encrypting the file. Some Microsoft documentation says, the IV does not have to be secret (look at the bottom of the section titled 'Secret-Key Encryption' of Microsoft's Cryptography Overview) and some does. Point is, without the correct IV you cannot decrypt the data properly, and therefore we save it here.
The last two bytes is used to store a value indicating which of the .NET
SymmetricAlgorithm
derived classes were used for encryption. The decryption functions of our component do not have aprovider
parameter. The provider is determined by the value saved in the header. - Lastly, our component contains two events that expose the
CryptoEventArgs
class. The events only fire for file operations and the class exposes properties relating to the progress of the operation. The only writeable property of the class isCancel
. Set this property toTrue
if you wish to cancel the operation. The sample application included in the download uses this functionality.
History
- 2005/12/28: First release! Please rate the work and leave some comments!