Recipe 14.3 Encrypting and Decrypting a File
Problem
You have sensitive information that must
be encrypted before it is written to a file that might be in a
nonsecure area. This information must also be decrypted before it is
read back in to the application.
Solution
Use multiple
cryptography providers and write the
data to a file in encrypted format. This is accomplished in the
following class, whose constructor expects an instance of the
System.Security.Cryptography.SymmetricAlgorithm
class and a path for the file. The
SymmetricAlgorithm class is an abstract base class
for all cryptographic providers in .NET, so we can be reasonably
assured that this class could be extended to cover all of them. This
example implements support for TripleDES and Rijndael. It could
easily be extended for DES and RC2, which are also provided by the
framework.
The following namespaces are needed for this solution:
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
The
class SecretFile can be used for TripleDES as
shown:
// Use TripleDES
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider( );
SecretFile secretTDESFile = new SecretFile(tdes,"tdestext.secret");
string encrypt = "My TDES Secret Data!";
Console.WriteLine("Writing secret data: {0}",encrypt);
secretTDESFile.SaveSensitiveData(encrypt);
// save for storage to read file
byte [] key = secretTDESFile.Key;
byte [] IV = secretTDESFile.IV;
string decrypt = secretTDESFile.ReadSensitiveData( );
Console.WriteLine("Read secret data: {0}",decrypt);
// release resources
tdes.Clear( );
To use
SecretFile with Rijndael, just substitute the
provider in the constructor like this:
// Use Rijndael
RijndaelManaged rdProvider = new RijndaelManaged( );
SecretFile secretRDFile = new SecretFile(rdProvider,"rdtext.secret");
string encrypt = "My Rijndael Secret Data!";
Console.WriteLine("Writing secret data: {0}",encrypt);
secretRDFile.SaveSensitiveData(encrypt);
// save for storage to read file
byte [] key = secretRDFile.Key;
byte [] IV = secretRDFile.IV;
string decrypt = secretRDFile.ReadSensitiveData( );
Console.WriteLine("Read secret data: {0}",decrypt);
// release resources
rdProvider.Clear( );
Here is the
implementation of SecretFile:
public class SecretFile
{
private byte[] savedKey = null;
private byte[] savedIV = null;
private SymmetricAlgorithm symmetricAlgorithm;
string path;
public byte[] Key
{
get { return savedKey; }
set { savedKey = value; }
}
public byte[] IV
{
get { return savedIV; }
set { savedIV = value; }
}
public SecretFile(SymmetricAlgorithm algorithm, string fileName)
{
symmetricAlgorithm = algorithm;
path = fileName;
}
public void SaveSensitiveData(string sensitiveData)
{
// Encode data string to be stored in encrypted file
byte[] encodedData = Encoding.Unicode.GetBytes(sensitiveData);
// Create FileStream and crypto service provider objects
FileStream fileStream = new FileStream(path,
FileMode.Create,
FileAccess.Write);
// Generate and save secret key and init vector
GenerateSecretKey( );
GenerateSecretInitVector( );
// Create crypto transform and stream objects
ICryptoTransform transform = symmetricAlgorithm.CreateEncryptor(savedKey,
savedIV);
CryptoStream cryptoStream =
new CryptoStream(fileStream, transform, CryptoStreamMode.Write);
// Write encrypted data to the file
cryptoStream.Write(encodedData, 0, encodedData.Length);
// Release all resources
cryptoStream.Close( );
transform.Dispose( );
fileStream.Close( );
}
public string ReadSensitiveData( )
{
// Create file stream to read encrypted file back
FileStream fileStream = new FileStream(path,
FileMode.Open,
FileAccess.Read);
//print out the contents of the encrypted file
BinaryReader binReader = new BinaryReader(fileStream);
Console.WriteLine("---------- Encrypted Data ---------");
int count = (Convert.ToInt32(binReader.BaseStream.Length));
byte [] bytes = binReader.ReadBytes(count);
char [] array = Encoding.Unicode.GetChars(bytes);
string encdata = new string(array);
Console.WriteLine(encdata);
Console.WriteLine("---------- Encrypted Data ---------\r\n");
// reset the file stream
fileStream.Seek(0,SeekOrigin.Begin);
// Create Decryptor
ICryptoTransform transform = symmetricAlgorithm.CreateDecryptor(savedKey,
savedIV);
CryptoStream cryptoStream = new CryptoStream(fileStream,
transform,
CryptoStreamMode.Read);
//print out the contents of the decrypted file
StreamReader srDecrypted = new StreamReader(cryptoStream,
new UnicodeEncoding( ));
Console.WriteLine("---------- Decrypted Data ---------");
string decrypted = srDecrypted.ReadToEnd( );
Console.WriteLine(decrypted);
Console.WriteLine("---------- Decrypted Data ---------");
// Release all resources
binReader.Close( );
srDecrypted.Close( );
cryptoStream.Close( );
transform.Dispose( );
fileStream.Close( );
return decrypted;
}
private void GenerateSecretKey( )
{
if(null != (symmetricAlgorithm as TripleDESCryptoServiceProvider))
{
TripleDESCryptoServiceProvider tdes;
tdes = symmetricAlgorithm as TripleDESCryptoServiceProvider;
tdes.KeySize = 192; // Maximum key size
tdes.GenerateKey( );
savedKey = tdes.Key;
}
else if(null != (symmetricAlgorithm as RijndaelManaged))
{
RijndaelManaged rdProvider;
rdProvider = symmetricAlgorithm as RijndaelManaged;
rdProvider.KeySize = 256; // Maximum key size
rdProvider.GenerateKey( );
savedKey = rdProvider.Key;
}
}
private void GenerateSecretInitVector( )
{
if(null != (symmetricAlgorithm as TripleDESCryptoServiceProvider))
{
TripleDESCryptoServiceProvider tdes;
tdes = symmetricAlgorithm as TripleDESCryptoServiceProvider;
tdes.GenerateIV( );
savedIV = tdes.IV;
}
else if(null != (symmetricAlgorithm as RijndaelManaged))
{
RijndaelManaged rdProvider;
rdProvider = symmetricAlgorithm as RijndaelManaged;
rdProvider.GenerateIV( );
savedIV = rdProvider.IV;
}
}
}
If the
SaveSensitiveData method is used to save the
following text to a file:
This is a test
This is sensitive data!
the
ReadSensitiveData method will display the
following information from this same file:
---------- Encrypted Data ---------
????????????????????????????????????????
---------- Encrypted Data ---------
---------- Decrypted Data ---------
This is a test
This is sensitive data!
---------- Decrypted Data ---------
Discussion
Encrypting data
is essential to many applications, especially ones that store
information in easily accessible locations. Once data is encrypted, a
decryption scheme is required to restore the data back to an
unencrypted form without losing any information. The same underlying
algorithms can be used to authenticate the source of a file or
message.
The encryption schemes used in this recipe are TripleDES and
Rijndael. The reason for using Triple DES are:
TripleDES employs symmetric encryption, meaning that a single private
key is used to encrypt and decrypt data. This process allows much
faster encryption and decryption, especially as the streams of data
become larger. TripleDES encryption is much harder to crack than the older DES
encryption. If you wish to use another type of encryption, this recipe can be
easily converted using any provider derived from the
SymmetricAlgorithm class.
The main drawback to TripleDES is that both the sender and receiver
must use the same key and Initialization Vector (IV) in order to
encrypt and decrypt the data successfully. If you wish to have an
even more secure encryption scheme, use the Rijndael scheme. This
type of encryption scheme is highly regarded as a solid encryption
scheme, since it is fast and can use larger key sizes than TripleDES.
However, it is still a symmetric cryptosystem, which means that it
relies on shared secrets. Use an asymmetric cryptosystem, such as RSA
or DSA, for a cryptosystem that uses shared public keys with private
keys that are never shared between parties.
See Also
See the "SymmetricAlgorithm Class,"
"TripleDESCryptoServiceProvider
Class," and "RijndaelManaged
Class" topics in the MSDN documentation.
|