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

File Encryption/Decryption with Hash Verification in C#

0.00/5 (No votes)
22 Oct 2004 1  
This article describes how to use C# and the .NET Framework to encrypt/decrypt files, and use hash algorithms to verify that the decrypted file is valid.

Sample Image - FileEncryptDecrypt.jpg

Introduction

This article is an expansion on a few of the articles here on CodeProject. I noticed that there are a lot of articles and posts dealing with Cryptography in the .NET Framework. These were all well and good. They got me started. Then, as I was progressing and using the System.Security.Cryptography namespace, I noticed that if the file was the right size and padded correctly, even using a bad password would output a file. This was not acceptable to me. So, I set out to write a class that would allow me to encrypt and then decrypt/verify that the contents had been written correctly.

Background

These articles started me down the road of .NET Cryptography:

Since none of these verified the output, I wrote a class to fix this.

The Code

The EncryptFile method:

/// <summary>

/// This takes an input file and encrypts it into the output file>

/// </summary>

/// <param name="inFile">the file to encrypt</param>

/// <param name="outFile">the file to write the encrypted data to</param>

/// <param name="password">the password for use as the key</param>

/// <param name="callback">the method to call to notify of progress</param>

public static void EncryptFile(string inFile, string outFile, 
                    string password, CryptoProgressCallBack callback)
{
    using(FileStream fin = File.OpenRead(inFile),
          fout = File.OpenWrite(outFile))
    {
        long lSize = fin.Length; // the size of the input file for storing

        int size = (int)lSize;  // the size of the input file for progress

        byte[] bytes = new byte; // the buffer

        int read = -1; // the amount of bytes read from the input file

        int value = 0; // the amount overall read from the input file for progress


        // generate IV and Salt

        byte[] IV = GenerateRandomBytes(16);
        byte[] salt = GenerateRandomBytes(16);

        // create the crypting object

        SymmetricAlgorithm sma = CryptoHelp.CreateRijndael(password, salt);
        sma.IV = IV;            

        // write the IV and salt to the beginning of the file

        fout.Write(IV,0,IV.Length);
        fout.Write(salt,0,salt.Length);

        // create the hashing and crypto streams

        HashAlgorithm hasher = SHA256.Create();
        using(CryptoStream cout = new CryptoStream(fout,sma.CreateEncryptor(),
            CryptoStreamMode.Write),
              chash = new CryptoStream(Stream.Null,hasher,
                CryptoStreamMode.Write))
        {
            // write the size of the file to the output file

            BinaryWriter bw = new BinaryWriter(cout);
            bw.Write(lSize);

            // write the file cryptor tag to the file

            bw.Write(FC_TAG);

            // read and the write the bytes to the crypto stream 

            // in BUFFER_SIZEd chunks

            while( (read = fin.Read(bytes,0,bytes.Length)) != 0 )
            {
                cout.Write(bytes,0,read);
                chash.Write(bytes,0,read);    
                value += read;
                callback(0,size,value);
            }
            // flush and close the hashing object

            chash.Flush();
            chash.Close();

            // read the hash

            byte[] hash = hasher.Hash;

            // write the hash to the end of the file

            cout.Write(hash,0,hash.Length);

            // flush and close the cryptostream

            cout.Flush();
            cout.Close();
        }
    }
}

What is interesting about this method and makes it different than the other articles' methods, is the fact that I write out the IV and Salt to the beginning of the output file. This adds a little more security to the file. For more information on these terms, check out Ritter's Crypto Glossary. Then after those two arrays are written, I encrypt and write the file size and a special tag (arbitrarily generated by me). These allow for some simple verifications of the file. After this, I do the encryption of the file, while hashing the data. Once the input file is completely encrypted, I encrypt the hash and write it out. By putting the hash at the end, I am able to verify the contents after decryption.

The DecryptFile method:

/// <summary>

/// takes an input file and decrypts it to the output file

/// </summary>

/// <param name="inFile">the file to decrypt</param>

/// <param name="outFile">the to write the decrypted data to</param>

/// <param name="password">the password used as the key</param>

/// <param name="callback">the method to call to notify of progress</param>

public static void DecryptFile(string inFile, string outFile,
            string password, CryptoProgressCallBack callback)
{
    // NOTE:  The encrypting algo was so much easier...


    // create and open the file streams

    using(FileStream fin = File.OpenRead(inFile),
              fout = File.OpenWrite(outFile))
    {
        // the size of the file for progress notification

        int size = (int)fin.Length;
        // byte buffer

        byte[] bytes = new byte;
        int read = -1; // the amount of bytes read from the stream

        int value = 0;
        int outValue = 0; // the amount of bytes written out


        // read off the IV and Salt

        byte[] IV = new byte[16];
        fin.Read(IV,0,16);
        byte[] salt = new byte[16];
        fin.Read(salt,0,16);

        // create the crypting stream

        SymmetricAlgorithm sma = CryptoHelp.CreateRijndael(password,salt);
        sma.IV = IV;

        value = 32; // the value for the progress

        long lSize = -1; // the size stored in the input stream


        // create the hashing object, so that we can verify the file

        HashAlgorithm hasher = SHA256.Create();

        // create the cryptostreams that will process the file

        using(CryptoStream cin = new CryptoStream(fin,sma.CreateDecryptor(),
                CryptoStreamMode.Read),
                  chash = new CryptoStream(Stream.Null,hasher,
                    CryptoStreamMode.Write))
        {
            // read size from file

            BinaryReader br = new BinaryReader(cin);
            lSize = br.ReadInt64();
            ulong tag = br.ReadUInt64();

            if(FC_TAG != tag)
                throw new CryptoHelpException("File Corrupted!");

            //determine number of reads to process on the file

            long numReads = lSize / BUFFER_SIZE;

            // determine what is left of the file, after numReads

            long slack = (long)lSize % BUFFER_SIZE;

            // read the buffer_sized chunks

            for(int i = 0; i < numReads; ++i)
            {
                read = cin.Read(bytes,0,bytes.Length);
                fout.Write(bytes,0,read);
                    chash.Write(bytes,0,read);
                value += read;
                outValue += read;
                callback(0,size,value);
            }

            // now read the slack

            if(slack > 0)
            {
                read = cin.Read(bytes,0,(int)slack);
                fout.Write(bytes,0,read);
                chash.Write(bytes,0,read);
                value += read;
                outValue += read;
                callback(0,size,value);
            }
            // flush and close the hashing stream

            chash.Flush();
            chash.Close();

            // flush and close the output file

            fout.Flush();
            fout.Close();

            // read the current hash value

            byte[] curHash = hasher.Hash;

            // get and compare the current and old hash values

            byte[] oldHash = new byte[hasher.HashSize / 8];
            read = cin.Read(oldHash,0,oldHash.Length);
            if((oldHash.Length != read) || (!CheckByteArrays(oldHash,curHash)))
                throw new CryptoHelpException("File Corrupted!");
        }
        
        // make sure the written and stored size are equal

        if(outValue != lSize)
            throw new CryptoHelpException("File Sizes don't match!");
    }
}

During decryption, I reverse the actions of encryption. First, I read both the IV and Salt from the file. I use these to create the SymmetricAlgorithm. Second, I decrypt and read the file size and the tag. This is the first step in verification--if the tag is equal to the const tag in the class, I know the file is so far not corrupted. Now comes the decryption of the file data. This took a little work, because normally I would just keep reading from the file until I could not read anymore. But I put the hash at the end. So, I had to figure out how to read only the amount of data in the file size. I did this by using a little math:

Number of Reads = The File Size / The Buffer Size
Left Over Bytes To Read = The File Size modulo The Buffer Size
NOTE: Both of these are integer math

Now, I use a for loop for reading most of the data, and then read the left over bytes.

During these reads, I hashed the decrypted data.

Then I read off the hash that was written last and compared it to the newly created hash. If they were equal, the file was not corrupted and the correct password was used to decrypt the file. If not, the algorithm has caught the error.

Using the code

This code is pretty easy to use:

using nb;
public class TestClass
{
    string myPassword = "TESTING!@#_123__";
    string myPlainFile = "test.txt";
    string myEncryptedFile = "test.encrypted";
    string myDecryptedFile = "test.decrypted";

    private static void Callback(int min, int max, int value)
    {
        // do something with progress (progress bar or just ignore it)

    }

    [STAThread]
    static void Main()
    {
        CryptoProgressCallBack cb = new CryptoProgressCallBack(Callback);
        //Do the Encryption

        CryptoHelp.EncryptFile(myPlainFile, myEncryptedFile, myPassword, cb);
        //Do the decryption

        CryptoHelp.DecryptFile(myEncryptedFile,myDecryptedFile, myPassword, cb);
    }
}

Points of Interest

The things I learned were how to use the .NET Framework's hashing/crypting algorithms. These classes when used in conjunction allow for some pretty powerful data security and data verification. I was able to verify the files data with a minimum overhead.

I hope that this is a help to others who need to add some security to their applications.

History

  • 22 Oct 2004: 1.0 - Initial release.

    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