Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#
Article

File Encryption/Decryption with Hash Verification in C#

Rate me:
Please Sign up or sign in to vote.
4.73/5 (28 votes)
22 Oct 2004CPOL3 min read 259.8K   11.7K   102   44
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:

C#
/// <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[BUFFER_SIZE]; // 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:

C#
/// <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[BUFFER_SIZE];
        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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Software Developer
    United States United States
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    Questionyour article File Encryption/Decryption with Hash Verification in C# Pin
    jacobus_jake9-Mar-18 7:58
    jacobus_jake9-Mar-18 7:58 
    Questionhashing code and decryption,decryption Pin
    Member 1352031614-Nov-17 2:11
    Member 1352031614-Nov-17 2:11 
    QuestionArgument out of range exception for big files? Pin
    a.ajames9-May-12 12:18
    a.ajames9-May-12 12:18 
    BugAttention:Major Bug! Pin
    Srikanth Basappa23-Apr-12 16:52
    Srikanth Basappa23-Apr-12 16:52 
    GeneralRe: Attention:Major Bug! Pin
    Ray [NVR]11-Apr-13 3:27
    Ray [NVR]11-Apr-13 3:27 
    GeneralMy vote of 5 Pin
    Manoj Kumar Choubey29-Mar-12 23:30
    professionalManoj Kumar Choubey29-Mar-12 23:30 
    Generalsorry Pin
    nashwann16-Aug-10 14:33
    nashwann16-Aug-10 14:33 
    Questioncan i use this in a Web Application? Pin
    suresh_kumar_s7-Dec-09 19:21
    suresh_kumar_s7-Dec-09 19:21 
    AnswerRe: can i use this in a Web Application? Pin
    Nathan Blomquist12-Dec-09 11:59
    Nathan Blomquist12-Dec-09 11:59 
    GeneralRe: can i use this in a Web Application? Pin
    suresh_kumar_s13-Dec-09 18:15
    suresh_kumar_s13-Dec-09 18:15 
    GeneralSome small bugfixes Pin
    Kim Togo15-Oct-09 4:28
    professionalKim Togo15-Oct-09 4:28 
    QuestionI need help about Encryp and DesEncryp Tiff Files Pin
    Diego Maciado14-May-09 7:44
    Diego Maciado14-May-09 7:44 
    GeneralChecking integrity in memory Pin
    Luke DeStevens9-Jan-09 12:51
    Luke DeStevens9-Jan-09 12:51 
    GeneralRe: Checking integrity in memory Pin
    Nathan Blomquist9-Jan-09 17:54
    Nathan Blomquist9-Jan-09 17:54 
    QuestionHow does saving the IV and salt make it more secure? Pin
    Luke DeStevens9-Jan-09 6:54
    Luke DeStevens9-Jan-09 6:54 
    AnswerRe: How does saving the IV and salt make it more secure? Pin
    Nathan Blomquist9-Jan-09 11:14
    Nathan Blomquist9-Jan-09 11:14 
    GeneralRe: How does saving the IV and salt make it more secure? Pin
    Luke DeStevens9-Jan-09 12:47
    Luke DeStevens9-Jan-09 12:47 
    GeneralRe: How does saving the IV and salt make it more secure? Pin
    Nathan Blomquist9-Jan-09 17:51
    Nathan Blomquist9-Jan-09 17:51 
    GeneralRe: How does saving the IV and salt make it more secure? Pin
    Cafechess6-Mar-09 4:28
    Cafechess6-Mar-09 4:28 
    GeneralNice work Pin
    Josef Meile25-Jan-08 2:53
    Josef Meile25-Jan-08 2:53 
    QuestionDecrypt in PHP Pin
    raymondk21-Nov-07 5:51
    raymondk21-Nov-07 5:51 
    NewsTwo other related encryption articles in CodeProject ... Pin
    Tony Selke27-Sep-07 7:06
    Tony Selke27-Sep-07 7:06 
    QuestionWrong password Pin
    Istvan Pszota9-Aug-07 9:05
    Istvan Pszota9-Aug-07 9:05 
    AnswerRe: Wrong password Pin
    Nathan Blomquist26-Sep-07 7:36
    Nathan Blomquist26-Sep-07 7:36 
    GeneralPadding is invalid & can not be removed Pin
    pomeydey5-Jul-07 22:07
    pomeydey5-Jul-07 22:07 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.