Title: Encrypt or decrypt files in C#
The following EncryptFile and DecryptFile methods encrypt or decrypt files at a very high level.
// Encrypt or decrypt a file, saving the results in another file.
public static void EncryptFile(string password,
string in_file, string out_file)
{
CryptFile(password, in_file, out_file, true);
}
public static void DecryptFile(string password,
string in_file, string out_file)
{
CryptFile(password, in_file, out_file, false);
}
These methods simply call the following CryptFile method, which also works at a high level.
The main program calls these methods to encrypt and decrypt the text in a file. It uses the techniques described in Convert between byte arrays and hexadecimal strings in C# to display the encrypted file as a sequence of hexadecimal values.
public static void CryptFile(string password,
string in_file, string out_file, bool encrypt)
{
// Create input and output file streams.
using (FileStream in_stream =
new FileStream(in_file, FileMode.Open, FileAccess.Read))
{
using (FileStream out_stream =
new FileStream(out_file, FileMode.Create,
FileAccess.Write))
{
// Encrypt/decrypt the input stream into
// the output stream.
CryptStream(password, in_stream, out_stream, encrypt);
}
}
}
This method creates input and output streams attached to the input and output files. It then passes them to the following CryptStream method to do all of the real work.
// Encrypt the data in the input stream into the output stream.
public static void CryptStream(string password,
Stream in_stream, Stream out_stream, bool encrypt)
{
// Make an AES service provider.
AesCryptoServiceProvider aes_provider =
new AesCryptoServiceProvider();
// Find a valid key size for this provider.
int key_size_bits = 0;
for (int i = 1024; i > 1; i--)
{
if (aes_provider.ValidKeySize(i))
{
key_size_bits = i;
break;
}
}
Debug.Assert(key_size_bits > 0);
Console.WriteLine("Key size: " + key_size_bits);
// Get the block size for this provider.
int block_size_bits = aes_provider.BlockSize;
// Generate the key and initialization vector.
byte[] key = null;
byte[] iv = null;
byte[] salt = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6,
0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
MakeKeyAndIV(password, salt, key_size_bits, block_size_bits,
out key, out iv);
// Make the encryptor or decryptor.
ICryptoTransform crypto_transform;
if (encrypt)
{
crypto_transform = aes_provider.CreateEncryptor(key, iv);
}
else
{
crypto_transform = aes_provider.CreateDecryptor(key, iv);
}
// Attach a crypto stream to the output stream.
// Closing crypto_stream sometimes throws an
// exception if the decryption didn't work
// (e.g. if we use the wrong password).
try
{
using (CryptoStream crypto_stream =
new CryptoStream(out_stream, crypto_transform,
CryptoStreamMode.Write))
{
// Encrypt or decrypt the file.
const int block_size = 1024;
byte[] buffer = new byte[block_size];
int bytes_read;
while (true)
{
// Read some bytes.
bytes_read = in_stream.Read(buffer, 0, block_size);
if (bytes_read == 0) break;
// Write the bytes into the CryptoStream.
crypto_stream.Write(buffer, 0, bytes_read);
}
} // using crypto_stream
}
catch
{
}
crypto_transform.Dispose();
}
The basic idea is to make a cryptographic service provider and attach it to a stream. As you write into the stream, the provider automatically encrypts or decrypts the data. The details are in creating and initializing the provider.
The method creates a new AesCryptoServiceProvider to use the AES (Advanced Encryption Standard) encryption method.
Next the program must make a key and initialization vector (IV) to initialize the service provider. It starts by finding a supported key size. It starts with a key size of 1,024 and reduces it until the provider's ValidKeySize method returns true. The key size you get will depend on things such as which version of Windows you are using.
Note: If you will encrypt and decrypt files on different computers, they must be able to use the same key size. You may need to set this value yourself if one computer can use a larger key than the other.
The program then calls the MakeKeyAndIV method described shortly to create a key and IV. The salt is an array of pseudo-random bytes that you initialize to make breaking the code with a dictionary attack harder. Pick your own set of values for this; don't use the values shown here.
The method then creates an encryptor or a decryptor, depending on whether it must encrypt or decrypt the file.
The rest of the method looks messy but is straightforward. It makes a CryptoStream that associates the encryptor/decryptor with the output stream. It then writes into the stream. As the data passes through the stream, the encryptor/decryptor does its magic and the output stream holds the result.
The following code shows the MakeKeyAndIV method.
// Use the password to generate key bytes.
private static void MakeKeyAndIV(string password, byte[] salt,
int key_size_bits, int block_size_bits,
out byte[] key, out byte[] iv)
{
Rfc2898DeriveBytes derive_bytes =
new Rfc2898DeriveBytes(password, salt, 1000);
key = derive_bytes.GetBytes(key_size_bits / 8);
iv = derive_bytes.GetBytes(block_size_bits / 8);
}
This code creates a new Rfc2898DeriveBytes object, passing its constructor your password, salt, and an iteration number. The object applies its operation for the indicated number of iterations to make its result "more random." In this case, it applies a pseudo-random number generator based on the HMACSHA1 algorithm 1000 times to generate its bytes.
The method then uses the object's GetBytes methods to get the key and IV bytes that the program needs to initialize the cryptographic service provider.
Very Important Note: Never store a password inside a program. If you do, a clever attacker can break open your program, read the password, and decrypt whatever the program wants to keep secret. This is particularly easy for .NET programs where it's relatively easy to read the program's IL code. A much better approach is to make the user enter the password at run time.
Also note that the password must match exactly to decode a file. If the password is off by even a single character, the result will be complete gibberish.
Download the example to experiment with it and to see additional details.
|