
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:
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;
int size = (int)lSize;
byte[] bytes = new byte;
int read = -1;
int value = 0;
byte[] IV = GenerateRandomBytes(16);
byte[] salt = GenerateRandomBytes(16);
SymmetricAlgorithm sma = CryptoHelp.CreateRijndael(password, salt);
sma.IV = IV;
fout.Write(IV,0,IV.Length);
fout.Write(salt,0,salt.Length);
HashAlgorithm hasher = SHA256.Create();
using(CryptoStream cout = new CryptoStream(fout,sma.CreateEncryptor(),
CryptoStreamMode.Write),
chash = new CryptoStream(Stream.Null,hasher,
CryptoStreamMode.Write))
{
BinaryWriter bw = new BinaryWriter(cout);
bw.Write(lSize);
bw.Write(FC_TAG);
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);
}
chash.Flush();
chash.Close();
byte[] hash = hasher.Hash;
cout.Write(hash,0,hash.Length);
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:
public static void DecryptFile(string inFile, string outFile,
string password, CryptoProgressCallBack callback)
{
using(FileStream fin = File.OpenRead(inFile),
fout = File.OpenWrite(outFile))
{
int size = (int)fin.Length;
byte[] bytes = new byte;
int read = -1;
int value = 0;
int outValue = 0;
byte[] IV = new byte[16];
fin.Read(IV,0,16);
byte[] salt = new byte[16];
fin.Read(salt,0,16);
SymmetricAlgorithm sma = CryptoHelp.CreateRijndael(password,salt);
sma.IV = IV;
value = 32;
long lSize = -1;
HashAlgorithm hasher = SHA256.Create();
using(CryptoStream cin = new CryptoStream(fin,sma.CreateDecryptor(),
CryptoStreamMode.Read),
chash = new CryptoStream(Stream.Null,hasher,
CryptoStreamMode.Write))
{
BinaryReader br = new BinaryReader(cin);
lSize = br.ReadInt64();
ulong tag = br.ReadUInt64();
if(FC_TAG != tag)
throw new CryptoHelpException("File Corrupted!");
long numReads = lSize / BUFFER_SIZE;
long slack = (long)lSize % BUFFER_SIZE;
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);
}
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);
}
chash.Flush();
chash.Close();
fout.Flush();
fout.Close();
byte[] curHash = hasher.Hash;
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!");
}
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)
{
}
[STAThread]
static void Main()
{
CryptoProgressCallBack cb = new CryptoProgressCallBack(Callback);
CryptoHelp.EncryptFile(myPlainFile, myEncryptedFile, myPassword, cb);
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.