Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / encryption

Private Folder Locker

4.78/5 (21 votes)
26 Apr 2013CPOL5 min read 76.4K   10.2K  
Folder Encrypted / Decrypter with standolone function

Introduction

I find myself searching again for an app and can't find a good / free app that does what I want. I decided to code my own as always. The code encrypts and decrypts a folder and all the files it can find inside that folder and its sub folders. It also has a standalone function that encapsulates all the files in a self extracting / decrypting application called locker.exe.

Background

The main aspects I will focus on in this article are the encryption / decryption of the files, the resource management of the files for the standalone app, and the code to compile such a standalone app at runtime.

Using the code

First I will focus on the encryption and decryption methods. Encrypting the file is made possible by using the EncryptFile method.

C#
private static void EncryptFile(string inputFile, byte[] key)
{
    try
    {
        string ext = Path.GetExtension(inputFile);
        string outputFile = inputFile.Replace(ext, "_enc" + ext);

        //Prepare the file for encryption by getting it into a stream
        string cryptFile = outputFile;
        FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

        //Setup the Encryption Standard using Write mode
        RijndaelManaged rijndaelCrypto = new RijndaelManaged();
        CryptoStream cs = new CryptoStream(fsCrypt, 
          rijndaelCrypto.CreateEncryptor(key, key), CryptoStreamMode.Write);

        //Write the encrypted file stream
        FileStream fsIn = new FileStream(inputFile, FileMode.Open);
        int data;
        while ((data = fsIn.ReadByte()) != -1)
        {
            cs.WriteByte((byte)data);
        }

        //Close all the Writers
        fsIn.Close();
        cs.Close();
        fsCrypt.Close();

        //Delete the original file
        File.Delete(inputFile);
        //Rename the encrypted file to that of the original
        File.Copy(outputFile, inputFile);
        File.Delete(outputFile);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
} 

Version 1 made use of the MD5CryptoServiceProvider class. There are some issues with this class where you will get an error if you don't encode the password key. By using UTF8 and MD5CryptoServiceProvider we ensure that the key is always a 128 bit or 16 byte key. This is required by the CryptoStream object.

Since MD5 is easily cracked Version 2 makes use of the PasswordDerivedBytes class instead (thanks RobTeixeira). This class not only requires the private and public keys, but also requires a salt that ensures that every encrypted password is unique. The included EncryptionUtils class has two methods for generating Random Salts but isn't used in the PrivateFolderLocker. Instead the file name is used to generate a Salt. 

C#
public static byte[] EncryptPassword(string clearText, string password, string salt)
{
    byte[] saltBytes = Encoding.Unicode.GetBytes(salt);
    byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
    PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, saltBytes);

    byte[] encryptedData = EncryptPW(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));
    //return Convert.ToBase64String(encryptedData); //For returning string instead
    return encryptedData;
} 

Decrypting the file again is made possible by using the DecryptFile method  

C#
private static void DecryptFile(string inputFile, byte[] key)
{
    string ext = Path.GetExtension(inputFile);
    string outputFile = inputFile.Replace(ext, "_enc" + ext);

    //Prepare the file for decryption by getting it into a stream
    FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);

    //Setup the Decryption Standard using Read mode
    RijndaelManaged rijndaelCrypto = new RijndaelManaged();
    CryptoStream cs = new CryptoStream(fsCrypt, 
      rijndaelCrypto.CreateDecryptor(key, key), CryptoStreamMode.Read);

    //Write the decrypted file stream
    FileStream fsOut = new FileStream(outputFile, FileMode.Create);
    try
    {
        int data;
        while ((data = cs.ReadByte()) != -1)
        { fsOut.WriteByte((byte)data); }

        //Close all the Writers
        fsOut.Close();
        cs.Close();
        fsCrypt.Close();

        //Delete the original file
        File.Delete(inputFile);
        //Rename the encrypted file to that of the original
        File.Copy(outputFile, inputFile);
        File.Delete(outputFile);
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        fsOut = null;
        cs = null;
        fsCrypt = null;
    }
} 

The main difference between the EncryptFile and DecryptFile methods is the mode in which the CryptoStream object is placed. Encryption uses CryptoStreamMode.Write and decryption uses CryptoSreamMode.Read. 

Since there isn't much use in encrypting and decrypting single files a recursive method was written to ease the process. 

C#
private static void GetAllFilesInDir(DirectoryInfo dir, string searchPattern)
{
    // list the files
    try
    {
        foreach (FileInfo f in dir.GetFiles(searchPattern))
        {
            //Console.WriteLine("File {0}", f.FullName);
            files.Add(f);
        }
    }
    catch
    {
        Console.WriteLine("Directory {0}  \n could not be accessed!!!!", dir.FullName);
        return;  // We already got an error trying to access dir so don't try to access it again
    }

    // process each directory
    // If I have been able to see the files in the directory I should also be able 
    // to look at its directories so I don't think I should place this in a try catch block
    foreach (DirectoryInfo d in dir.GetDirectories())
    {
        folders.Add(d);
        GetAllFilesInDir(d, searchPattern);
    }
} 

Version 3 of this utility will build a XML database of the files and directories so that extraction remembers the folder structure. As an addition to the XML database a Salt field will store a much stronger Salt for much better security.

I made use of lists to store the files and folders found:

C#
static List<FileInfo> files = new List<FileInfo>();
static List<DirectoryInfo> folders = new List<DirectoryInfo>(); 

To use this recursive method I created individual methods for encrypting and decrypting folders. 

C#
public static bool EncryptedFolder(string folderDirectory, string pword,bool compress)
{
    bool status = false;
    string fileLocation = "";
    string salt = "";
    byte[] encPW = null;

    try
    {
        status = Directory.Exists(folderDirectory);

        if (status)
        {
            DirectoryInfo di = new DirectoryInfo(folderDirectory);

            //Clear Folder and File list
            folders = new List<DirectoryInfo>();
            files = new List<FileInfo>();

            //Build new Folder and File list
            GetAllFilesInDir(di, "*");

            
            foreach (FileInfo fi in files)
            {
                fileLocation = fi.FullName;

                if (compress)
                {
                    fileLocation += ".zip";
                }

                if (compress)
                {
                    //Compress the file using 7Zip's LZMA compression
                    CompressFileLZMA(fi.FullName, fileLocation);

                    //Delete the original file
                    if (File.Exists(fi.FullName))
                    {
                        File.Delete(fi.FullName);
                    }
                }

                //Build the Encrypted Password with a unique salt based on the file's info
                string fileData = string.Format("{0}", fi.Name.Substring(0, fi.Name.IndexOf(".")));
                salt = Convert.ToBase64String(GetBytes(fileData));
                encPW = EncryptPassword(pword, "!PrivateLocker-2013", salt);
                string strPW = Convert.ToBase64String(encPW);

                EncryptFile(fileLocation, encPW);

                
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        status = false;
    }

    return status;
} 

Version 2 extended the original method by adding 7Zip LZMA compression. This ensures that when the standalone option is used, the Locker.exe is as small as possible.

Now that we can encrypt and decrypt entire folders I figured why must I carry the app around every time I want to decrypt the files. I decided to create a way to have the encrypted files embedded in a standalone exe.

For this I had to get the encrypted files into an app and embed it as a resource. This called for some runtime code compilation. For this I had to make use of the CodeDomProvider

C#
public static bool CompileExecutable(string sourceName, string resourceDirectory)
{
    FileInfo sourceFile = new FileInfo(sourceName);
    CodeDomProvider provider = null;
    bool compileOk = false;

    // Select the code provider based on the input file extension. 
    if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".CS")
    {
        provider = CodeDomProvider.CreateProvider("CSharp");
    }
    else if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".VB")
    {
        provider = CodeDomProvider.CreateProvider("VisualBasic");
    }
    else 
    {
        Console.WriteLine("Source file must have a .cs or .vb extension");
    }

    if (provider != null)
    {
        // Format the executable file name. 
        // Build the output assembly path using the current directory 
        String exeName = String.Format(@"{0}\Locker.exe", resourceDirectory);
        
        CompilerParameters cp = new CompilerParameters();

        // Generate an executable instead of  
        // a class library.
        cp.GenerateExecutable = true;

        // Specify the assembly file name to generate.
        cp.OutputAssembly = exeName;

        // Save the assembly as a physical file.
        cp.GenerateInMemory = false;

        // Set whether to treat all warnings as errors.
        cp.TreatWarningsAsErrors = false;

        //Add the Resources
        resourceFiles.Clear();
        GetAllFilesInDir(new DirectoryInfo(resourceDirectory), "*");
        foreach (FileInfo file in resourceFiles)
        {
            if (file.Name != "standalone.cs")
            {
                cp.EmbeddedResources.Add(file.FullName);
            }
            if (file.Name == "Lzma.dll")
            {
                cp.ReferencedAssemblies.Add(file.FullName);
            }
            if (file.Name == "key2.ico")
            {
                //Add Icon
                cp.CompilerOptions = string.Format("/win32icon:{0}", file.FullName);
            }
        }
        // Invoke compilation of the source file.
        CompilerResults cr = provider.CompileAssemblyFromFile(cp, 
            sourceName);

        if(cr.Errors.Count > 0)
        {
            // Display compilation errors.
            Console.WriteLine("Errors building {0} into {1}",  
                sourceName, cr.PathToAssembly);
            foreach(CompilerError ce in cr.Errors)
            {
                Console.WriteLine("  {0}", ce.ToString());
                Console.WriteLine();
            }
        }
        else
        {
            // Display a successful compilation message.
            Console.WriteLine("Source {0} built into {1} successfully.",
                sourceName, cr.PathToAssembly);
        }

        // Return the results of the compilation. 
        if (cr.Errors.Count > 0)
        {
            compileOk = false;
        }
        else 
        {
            compileOk = true;
        }

        if (resourceFiles != null)
        {
            foreach (FileInfo file in resourceFiles)
            {
                try
                {
                    File.Delete(file.FullName);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                
            }
        }
    }
    return compileOk;
} 

Version 2 of the app made for some interesting modifications to the CompileExecutable method. Since the standalone exe now uses LZMA.dll, I had to include it in the resources and extract it at runtime. 

I created a a little console app (standalone.cs) that makes use of the decryption methods coded earlier and then created a way to extract the embedded resource files dynamically.   

C#
private static void ExtractResources(string dir)
{
    try
    {
        string fInfo = "";
        Assembly asm = Assembly.GetExecutingAssembly();
        Stream fstr = null;

        //Create The output Directory if it Doesn't Exist
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }

        //Loop thru all the resources and Extract them
        foreach (string resourceName in asm.GetManifestResourceNames())
        {
            fInfo = dir + @"\" + resourceName.Replace(asm.GetName().Name + ".Resources.", "");
            fstr = asm.GetManifestResourceStream(resourceName);

            if(fInfo.Contains("Lzma"))
            {
                fInfo = System.Environment.CurrentDirectory + "\\" + 
                  resourceName.Replace(asm.GetName().Name + ".Resources.", "");
            }

            if (fstr != null && !fInfo.Contains("key2.ico"))
            {
                SaveStreamToFile(fInfo, fstr);
            }
        }

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}  

I thus called this ExtractResources method to extract the standalone.cs file that I embedded in the main app. In Version 2 the LZMA.dll is extracted to make use of the 7Zip decompress method. 

The standalone.cs file path is then passed to the CompileExecutable method after extraction and the folder chosen for encryption is passed as the resource path. The CompileExecutable method then compiles the standalone.cs file and loops through all the files chosen for encryption. It adds these files as embedded resources by using the CompilerParameters object cp. cp.EmbeddedResources.Add(fileName) thus  embeds the encrypted files in the compiled exe.  

This process creates an exe called Locker.exe and is essentially made up of the code found in the standalone.cs file.  

So... what is contained in the standalone.cs file? Well, it's a pretty straightforward console app that requests the user for a password. It then makes use of the ExtractResources method to extract all its embedded resources. It then makes use of the DecryptFile method to decrypt the extracted files.  

Points of Interest

During the coding of this app I learned that resource management sucks and isn't as straightforward as one would imagine. I had to make use of a "little black magic" (i.e., Reflection) to see all the resources embedded in the standalone app. I also learned by placing the resources in a Resource.resx file means that you won't be able to get the file's extension back. This made that I embed the resources directly into the app rather that embedding it in a resource file. 

History

  • V1: This is version one of the app and I'm pretty sure some CodeProject gurus can spruce it up quite some. There is still an issue where the standalone app doesn't recreate the sub-directories. This can be fixed with a hack where an XML list of the files is added at the end of compilation. During extraction we can use this XML list to extract the resources to the correct sub directories. By making use of the folders list created during the recursive search for files this XML file creation should be easy enough.
  • V2: This version of the PrivateFolderLocker makes use of much better encryption standards. It implements the PasswordDeriveBytes class and generates a Salt per file.

License

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