Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET cryptography library for files and strings

0.00/5 (No votes)
29 Dec 2005 5  
This is a VB.NET wrapper for the .NET framework cryptography classes (HashAlgorithm, SymmetricAlgorithm) for working with strings and files.

Sample app using CryptoLib.

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 the While loop to receive bytes from the provider.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 the provider.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 the provider.Hash property after the provider.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 of FileStream to allow us to continue working with the file in .NET. The important bit in all this is the FlagsAndAttributes.FILE_FLAG_WRITE_THROUGH enumeration value that is passed to CreateFile 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 a provider 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 is Cancel. Set this property to True 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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here