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.
private static void EncryptFile(string inputFile, byte[] key)
{
try
{
string ext = Path.GetExtension(inputFile);
string outputFile = inputFile.Replace(ext, "_enc" + ext);
string cryptFile = outputFile;
FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);
RijndaelManaged rijndaelCrypto = new RijndaelManaged();
CryptoStream cs = new CryptoStream(fsCrypt,
rijndaelCrypto.CreateEncryptor(key, key), CryptoStreamMode.Write);
FileStream fsIn = new FileStream(inputFile, FileMode.Open);
int data;
while ((data = fsIn.ReadByte()) != -1)
{
cs.WriteByte((byte)data);
}
fsIn.Close();
cs.Close();
fsCrypt.Close();
File.Delete(inputFile);
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.
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 encryptedData;
}
Decrypting the file again is made possible by using the DecryptFile
method
private static void DecryptFile(string inputFile, byte[] key)
{
string ext = Path.GetExtension(inputFile);
string outputFile = inputFile.Replace(ext, "_enc" + ext);
FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
RijndaelManaged rijndaelCrypto = new RijndaelManaged();
CryptoStream cs = new CryptoStream(fsCrypt,
rijndaelCrypto.CreateDecryptor(key, key), CryptoStreamMode.Read);
FileStream fsOut = new FileStream(outputFile, FileMode.Create);
try
{
int data;
while ((data = cs.ReadByte()) != -1)
{ fsOut.WriteByte((byte)data); }
fsOut.Close();
cs.Close();
fsCrypt.Close();
File.Delete(inputFile);
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.
private static void GetAllFilesInDir(DirectoryInfo dir, string searchPattern)
{
try
{
foreach (FileInfo f in dir.GetFiles(searchPattern))
{
files.Add(f);
}
}
catch
{
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return;
}
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:
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.
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);
folders = new List<DirectoryInfo>();
files = new List<FileInfo>();
GetAllFilesInDir(di, "*");
foreach (FileInfo fi in files)
{
fileLocation = fi.FullName;
if (compress)
{
fileLocation += ".zip";
}
if (compress)
{
CompressFileLZMA(fi.FullName, fileLocation);
if (File.Exists(fi.FullName))
{
File.Delete(fi.FullName);
}
}
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
.
public static bool CompileExecutable(string sourceName, string resourceDirectory)
{
FileInfo sourceFile = new FileInfo(sourceName);
CodeDomProvider provider = null;
bool compileOk = false;
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)
{
String exeName = String.Format(@"{0}\Locker.exe", resourceDirectory);
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = true;
cp.OutputAssembly = exeName;
cp.GenerateInMemory = false;
cp.TreatWarningsAsErrors = false;
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")
{
cp.CompilerOptions = string.Format("/win32icon:{0}", file.FullName);
}
}
CompilerResults cr = provider.CompileAssemblyFromFile(cp,
sourceName);
if(cr.Errors.Count > 0)
{
Console.WriteLine("Errors building {0} into {1}",
sourceName, cr.PathToAssembly);
foreach(CompilerError ce in cr.Errors)
{
Console.WriteLine(" {0}", ce.ToString());
Console.WriteLine();
}
}
else
{
Console.WriteLine("Source {0} built into {1} successfully.",
sourceName, cr.PathToAssembly);
}
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.
private static void ExtractResources(string dir)
{
try
{
string fInfo = "";
Assembly asm = Assembly.GetExecutingAssembly();
Stream fstr = null;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
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.