Click here to Skip to main content
12,348,331 members (68,769 online)
Click here to Skip to main content
Add your own
alternative version

Stats

16.5K views
715 downloads
41 bookmarked
Posted

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.

You may also be interested in...

Comments and Discussions

 
QuestionThis is a bit suspect (or maybe not)... Pin
Member 1124303317-Nov-14 23:00
memberMember 1124303317-Nov-14 23:00 
AnswerRe: This is a bit suspect (or maybe not)... Pin
Matthew Givens20-Feb-15 7:48
memberMatthew Givens20-Feb-15 7:48 
QuestionAny Idea about doing same thing in windows phone Pin
Robin Kumar4-Sep-14 3:43
memberRobin Kumar4-Sep-14 3:43 
AnswerRe: Any Idea about doing same thing in windows phone Pin
Matthew Givens4-Sep-14 4:16
memberMatthew Givens4-Sep-14 4:16 
QuestionA couple of thoughts Pin
OriginalGriff31-Aug-14 5:41
protectorOriginalGriff31-Aug-14 5:41 
AnswerRe: A couple of thoughts Pin
Matthew Givens2-Sep-14 3:01
memberMatthew Givens2-Sep-14 3:01 
GeneralMy vote of 5 Pin
Mihai MOGA13-Aug-14 3:15
professionalMihai MOGA13-Aug-14 3:15 
Questionusage of "NOWISTHE" and "WINTEROF" Pin
AmirHashemZadeh22-Jul-14 9:14
memberAmirHashemZadeh22-Jul-14 9:14 
AnswerRe: usage of "NOWISTHE" and "WINTEROF" Pin
Matthew Givens22-Jul-14 13:03
memberMatthew Givens22-Jul-14 13:03 
GeneralExcellent example of usage of encryption routines, but still potential information leakage Pin
Tom Spink15-Jul-14 12:43
memberTom Spink15-Jul-14 12:43 
GeneralRe: Excellent example of usage of encryption routines, but still potential information leakage Pin
Matthew Givens19-Jul-14 8:32
memberMatthew Givens19-Jul-14 8:32 
GeneralRe: Excellent example of usage of encryption routines, but still potential information leakage Pin
1337Architect2-Sep-14 20:41
professional1337Architect2-Sep-14 20:41 
QuestionGood thoughts Pin
Garth J Lancaster14-Jul-14 22:51
memberGarth J Lancaster14-Jul-14 22:51 
AnswerRe: Good thoughts Pin
Matthew Givens19-Jul-14 8:43
memberMatthew Givens19-Jul-14 8:43 
GeneralMy vote of 5 Pin
Volynsky Alex14-Jul-14 10:40
professionalVolynsky Alex14-Jul-14 10:40 

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.

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