Click here to Skip to main content
Click here to Skip to main content

Using Encrypted Files Without Decrypting To Disk

, 2 Sep 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Showing how to decrypt files in memory in order to maintain data security.

Introduction

Encryption and decryption often occur on a file basis... that is, an encrypted file is decrypted to another file on the disk, and applications pull from the decrypted file. Assuming that encryption was used in the first place to maintain security and/or limit access to copyrighted or sensitive material, decrypting to a disk file defeats the purpose of using encryption in the first place.

Assume, for example, that an application uses copyrighted audio files that are okay to be used by the application but should never be used outside it. In order to restrict that access, the audio files are encrypted. Having the application decrypt the file to disk is a problem, because even if the file is automatically deleted immediately after being played, it still existed on the disk and can theoretically be recovered.

A better solution would be to have the application decrypt the file in memory, leaving no trace on the disk of the unencrypted data. (For a caveat to this statement, please see the "Points of Interest" section.)

Background

Readers are asumed to have knowledge of basic input/output operations, i.e. reading and writing files using the FileStream and related classes.

Using the code

All of the encryption/decryption code in the sample solution is contained in the EncryptionEngine class, which uses only .NET native classes. Simply include that class in a project and use the available methods.

The sample solution provided with this article contains two windows forms applications. The first uses the EncryptionEngine to allow the user to select and encrypt text and wav files. The second allows the user to select an encrypted wav file and play it using .NET Media.Soundplayer, or to select a text file and have the contents of that file displayed in a pop-up form.

Let's start with the encryption process. For this sample application, I chose to use DES encryption, though the code could easily be modified to use a more robust encryption method.

First, the raw file is read into an internal buffer.

// Open the file and prepare the buffer
FileInfo info = new FileInfo(sourceFile);
FileStream input = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);
byte[] dta = new byte[info.Length];

// Read the raw file into the buffer and close it
input.Read(dta, 0, (int)info.Length);
input.Close();

Next, the data buffer is encrypted and written to the output file.

FileStream output = new FileStream(destFile, FileMode.Create, FileAccess.Write);

// Setup the encryption object
DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

// Create a crptographic output stream for the already open file output stream
CryptoStream crStream = new CryptoStream(output, cryptic.CreateEncryptor(), CryptoStreamMode.Write);

// Write the data buffer to the encryption stream, then close everything
crStream.Write(dta, 0, dta.Length);
crStream.Close();
output.Close();

At this point, the file has been encrypted and saved under the new name. The full EncryptBinaryFile method looks like this:

public void EncryptBinaryFile(String sourceFile, String destFile)
{
    // Get file info, used to know the size of the file
    FileInfo info = new FileInfo(sourceFile);

    // Open the file for input
    FileStream input = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);

    // Create a data buffer large enough to hold the file -- Could be modified to read 
    // the file in smaller chunks.
    byte[] dta = new byte[info.Length];

    // Read the raw file into the buffer and close it
    input.Read(dta, 0, (int)info.Length);
    input.Close();

    // Open the file for output
    FileStream output = new FileStream(destFile, FileMode.Create, FileAccess.Write);

    // Setup the encryption object
    DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
    cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
    cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

    // Create a crptographic output stream for the already open file output stream
    CryptoStream crStream = new CryptoStream(output, cryptic.CreateEncryptor(), 
                                             CryptoStreamMode.Write);

    // Write the data buffer to the encryption stream, then close everything
    crStream.Write(dta, 0, dta.Length);
    crStream.Close();
    output.Close();
}

Now that the file has been encrypted, we get to the meat of the problem, decrypting the file as it is being read in order to use it. For this, we'll look at two examples, displaying a text file and playing a wav file.

The LoadEncryptedTextFile method decrypts the encrypted file and loads it into an array of strings to be processed however the application needs to. In this case, it will eventually be passed to a popup form and displayed in a listbox.

The first step is to setup the encryption object.

DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

Note that it is set up exactly as it was in the encryption method. The next step is to open the file as a FileStream, then use that to establish the CryptoStream. Then, since this is a text file and we want to read it that way, we use the CryptoStream to establish the StreamReader.

FileStream input = new FileStream(sourceFile, FileMode.Open);
CryptoStream cryptoStream = new CryptoStream(input, cryptic.CreateDecryptor(), 
                                             CryptoStreamMode.Read);
StreamReader vStreamReader = new StreamReader(cryptoStream, Encoding.ASCII);

At this point, we treat it like we would any other input text file, reading data with the Read() and ReadLine() methods. The entire method looks like this:

public List<String> LoadEncryptedTextFile(String sourceFile)
{
    List<String> lines = new List<string>();

    DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
    cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
    cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

    // Open the file, decrypt the data stream, and send it to XmlTextReader
    FileStream input = new FileStream(sourceFile, FileMode.Open);
    CryptoStream cryptoStream = new CryptoStream(input, cryptic.CreateDecryptor(), 
                                                 CryptoStreamMode.Read);
    StreamReader vStreamReader = new StreamReader(cryptoStream, Encoding.ASCII);

    while (!vStreamReader.EndOfStream)
    {
        String line = vStreamReader.ReadLine();
        lines.Add(line);
    }
    vStreamReader.Close();
    cryptoStream.Close();
    input.Close();

    return lines;
}

Note that loading a list of strings isn't strictly necessary... each line could be processed immediately as it is read, instead of being saved to process later.

The procedure for playing an encrypted wav file works similarly, decrypting and reading the file into an internal buffer, then passing that buffer as a MemoryStream to the .NET SoundPlayer() method. We start by opening the file and setting up the decryptor.

FileInfo info = new FileInfo(sourceFile);
FileStream input = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);
DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

// Implement the decryptor
CryptoStream crStream = new CryptoStream(input, cryptic.CreateDecryptor(), 
                                         CryptoStreamMode.Read);

At this point the file is open and the decryption ready to start. So we build a BinaryReader and read the file into a buffer.

BinaryReader rdr = new BinaryReader(crStream);
byte[] dta = new byte[info.Length];
rdr.Read(dta, 0, (int)info.Length);

With the data stored in a byte array, we build a MemoryStream and feed that to the .NET SoundPlayer.

Stream stream = new MemoryStream(dta);
var MainPlayer = new SoundPlayer(stream);
MainPlayer.Play();

The entire PlayEncWav method looks like this:

public void PlayEncWav(String sourceFile)
{
    if (sourceFile.Length == 0)
                return;

    // Get the encrypted file and setup the decryption engine
    FileInfo info = new FileInfo(sourceFile);
    FileStream input = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);
    DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
    cryptic.Key = ASCIIEncoding.ASCII.GetBytes("NOWISTHE");
    cryptic.IV = ASCIIEncoding.ASCII.GetBytes("WINTEROF");

    // Implement the decryptor
    CryptoStream crStream = new CryptoStream(input, cryptic.CreateDecryptor(), 
                                                    CryptoStreamMode.Read);

    // Read the decrypted file into memory and convert to a memory stream
    BinaryReader rdr = new BinaryReader(crStream);
    byte[] dta = new byte[info.Length];
    rdr.Read(dta, 0, (int)info.Length);
    rdr.Close();
    crStream.Close();
    input.Close();

    Stream stream = new MemoryStream(dta);
    var MainPlayer = new SoundPlayer(stream);
    MainPlayer.Play();
    stream.Close();
}

And that's really all there is to it. Using encryption adds a layer of complexity to an application, but it's often worth the extra effort to treat datafiles in a more secure manner.

I have used these methods for a variety of purposes, including parsing encrypted XML files to be used by the application. (Hint: use the StreamReader (which is opened on the CryptoStream) to create an XmlTextReader.) The key point for every use is that the encryption for the files is never broken where others outside the application can get to it, i.e. on the disk. The integrity of the encrypted data is maintained even as the application decrypts it freely for its own purposes.

Points of Interest

The first and most obvious point to keep in mind is that the Key and IV for the DESCryptoServiceProvider must be the same in the decrypting application as it was in the encrypting application. They don't have to match the values I used in the example, but decryption isn't possible without the matching key data.

Because of the obvious limitation of DES encryption, i.e. the 8-character limit for the encryption key, feel free to modify the code for a more robust encryption method before actually using it in an application.

Strictly speaking, I didn't need to load the decrypted audio file into a buffer and pass that as a MemoryStream to the SoundPlayer... the player is a simple beast and doesn't allow seeking in the audio stream, so loading it all into memory isn't strictly necessary. However, if upgrading to a more robust player, such as NAudio, the audio file has to be completely in memory in order to allow seeking.

Update 09/02/2014: Decrypting a file to a memory stream doesn't completely remove the possibility that the decrypted file appears on the disk; if the application gets page-swapped at any point prior to being disposed of by the system GC, the decrypted memory buffer would be stored on the disk. Unfortunately, there's nothing we can do to prevent the application from being paged, all we can do is limit the amount of time that the decrypted buffer is being used. Once your program has finished using the decrypted buffer, it would be good practice to erase and release the memory buffer. This doesn't "fix" the problem, but it does greatly reduce the window of opportunity for paging to capture the decrypted buffer. Even with this potential problem, however, decrypting files to a memory stream is still a good idea and a strong method for maintaining data security.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matthew Givens
Software Developer (Senior)
United States United States
Matthew Givens has been a software developer since 1988, and has worked for numerous companies over the years. He currently works in the healthcare industry, developing with C# .NET and Sql-Server.

Comments and Discussions

 
QuestionThis is a bit suspect (or maybe not)... [modified] PinmemberMember 1124303318-Nov-14 0:00 
AnswerRe: This is a bit suspect (or maybe not)... PinmemberMatthew Givens20-Feb-15 8:48 
QuestionAny Idea about doing same thing in windows phone PinmemberRobin Kumar4-Sep-14 4:43 
AnswerRe: Any Idea about doing same thing in windows phone PinmemberMatthew Givens4-Sep-14 5:16 
QuestionA couple of thoughts PinprotectorOriginalGriff31-Aug-14 6:41 
AnswerRe: A couple of thoughts PinmemberMatthew Givens2-Sep-14 4:01 
GeneralMy vote of 5 PinprofessionalMihai MOGA13-Aug-14 4:15 
Questionusage of "NOWISTHE" and "WINTEROF" PinmemberAmirHashemZadeh22-Jul-14 10:14 
AnswerRe: usage of "NOWISTHE" and "WINTEROF" PinmemberMatthew Givens22-Jul-14 14:03 
GeneralExcellent example of usage of encryption routines, but still potential information leakage PinmemberTom Spink15-Jul-14 13:43 
GeneralRe: Excellent example of usage of encryption routines, but still potential information leakage PinmemberMatthew Givens19-Jul-14 9:32 
GeneralRe: Excellent example of usage of encryption routines, but still potential information leakage Pinprofessional1337Architect2-Sep-14 21:41 
QuestionGood thoughts PinmemberGarth J Lancaster14-Jul-14 23:51 
AnswerRe: Good thoughts PinmemberMatthew Givens19-Jul-14 9:43 
GeneralMy vote of 5 PinprofessionalVolynsky Alex14-Jul-14 11:40 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150331.1 | Last Updated 2 Sep 2014
Article Copyright 2014 by Matthew Givens
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid