0
\$\begingroup\$

I am making a Cryptography application and I noticed that the encrypted file is approximately 2 times bigger because I am encoding the files in base64. Are there any file encodings that take less space? Here is the code that converts files into byte arrays:

Console.Write("Enter File Path: ");
docPath = Console.ReadLine();
extension = docPath.Substring(docPath.IndexOf(".")).Trim();
byte[] binarydata = File.ReadAllBytes(docPath);
text = System.Convert.ToBase64String(binarydata, 0, binarydata.Length);
var Encrypted = AESCryptography.Encrypt(text, m.ToString(), extension);
using (FileStream fs = File.Create(docPath.Substring(0,docPath.IndexOf(".")) + ".aent"))
{
    Byte[] info = new UTF8Encoding(true).GetBytes(Encrypted);
    // Add some information to the file.
    fs.Write(info, 0, info.Length);
}

and here is the Encryption class that does the actual encryption:

public static class AESCryptography
{
    private const int keysize = 256;
    public static string Encrypt(string plainText, string passPhrase, string extention)
    {
        byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
        {
            byte[] keyBytes = password.GetBytes(keysize / 8);
            using (RijndaelManaged symmetricKey = new RijndaelManaged())
            {
                symmetricKey.GenerateIV();
                symmetricKey.Mode = CipherMode.CBC;
                using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, symmetricKey.IV))
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                        {
                            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                            cryptoStream.FlushFinalBlock();
                            byte[] cipherTextBytes = memoryStream.ToArray();
                            return Convert.ToBase64String(cipherTextBytes) + "\n" + Convert.ToBase64String(symmetricKey.IV) + "\n" + extention;
                        }
                    }
                }
            }
        }
    }

    public static string Decrypt(string cipherText, string passPhrase, string initVector)
    {
        byte[] initVectorBytes = Convert.FromBase64String(initVector);
        byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
        using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
        {
            byte[] keyBytes = password.GetBytes(keysize / 8);
            using (RijndaelManaged symmetricKey = new RijndaelManaged())
            {
                symmetricKey.Mode = CipherMode.CBC;
                using (ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes))
                {
                    using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                        {
                            byte[] plainTextBytes = new byte[cipherTextBytes.Length];
                            int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                            return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                        }
                    }
                }
            }
        }
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ This question appears to be off-topic because it is about how to add a requested feature \$\endgroup\$ – Heslacher Aug 21 '14 at 11:34
  • 2
    \$\begingroup\$ Welcome to Code Review. We are not a code writing service, and as such, this question appears off-topic. With that said, if you were to remove your request for help implementing new features, this seems like it'd be a perfectly on-topic question and we'd gladly review your existing code and help you fix it up a bit. \$\endgroup\$ – nhgrif Aug 21 '14 at 11:37
  • \$\begingroup\$ @nhgrif fixed it. \$\endgroup\$ – javaseaayameradost Aug 21 '14 at 11:47
  • \$\begingroup\$ I feel this still needs more editing, Are there any file encodings that take less space? -> this looks like you are asking us to implement something or give you recommendations to implement something. Not sure if within the scope \$\endgroup\$ – bhathiya-perera Aug 21 '14 at 12:20
  • \$\begingroup\$ I think the question is okay as it is. I consider it a performance question. \$\endgroup\$ – RubberDuck Aug 28 '14 at 10:18
2
\$\begingroup\$

Let us analyze what the code is doing:

  1. reading a file by calling File.ReadAllBytes()
  2. converting these bytes to a base64 String
  3. passing this String to the Encrypt method
  4. converting the passed String to byte[]
  5. encrypting this byte[]
  6. converting the encrypted byte[] to base64
  7. converting the IV vector to base64
  8. returning the addition of 6., 7. and the extension
  9. converting the returned Stringto byte[] which is saved to a file with .aent extension

I can only see one reason why you want to do it in this way.

-> because you want to store the extension of the original file into the encrypted file.

Let us assume we don't want to store the extension of the original file inside the encrypted file, but we just append the .aent extension to the original filename.

This would have a lot of benefits. The code could be faster and the files could be smaller.

Why so ?

As CodesInChaos pointed out in the comments, Base64 increases the size of the data by 1.33 because for each 3 bytes you get 4 bytes back and the output is padded to always be a multiple of 4.
So starting with a file having a size of 1000 bytes after 2. you have 1336 bytes. After the encryption the conversion to base64 of the encrypted data, only assuming you still would have 1336 bytes, will result in 1784 bytes. Now adding the to base64 converted IV vector, which has a size of 16 for the default RijndaelManaged, will result in additional 24 bytes. The \n will result each in 2 bytes.

So all together: 1784 + 2 + 24 + 2 = 1812 almost a factor of 2.

So let us skip the base64 converting and the adding of the file extension.

Some remarks about the code

  • using of const for the keysize is good
  • using proper casing for methodnames and parameter is good except byte[] Encrypted
  • using the using keyword for using IDisposable objects is good

  • using a parametername plainText but also stating it could be a jpeg file is not so good
  • the way of getting the extension, instead of using built in methods is not so good

What you can do, but this is really a matter of taste, to reduce the indention of the code is stacking the using statements together like shown below. If we also extract the getting of the keybytes to a separate method, we loose one more indention and also the Encrypt() method gets shorter.

Refactoring

So let us start with the latter:

private static byte[] GetKeyBytes(string passPhrase)
{
    using (PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null))
    {
        byte[] keyBytes = password.GetBytes(keysize / 8);
        return keyBytes;
    }
}

Now we will change the String plainText input parameter to byte[] plainBytes and the returntype to byte[]

But wait, as we get two byte arrays, one of the encrypted data and one of the IV vector data, we need to combine these to one array. So let us introduce a method first

private static byte[] Combine(byte[] first, byte[] second)
{
    byte[] ret = new byte[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

And now the Encrypt method

public static byte[] Encrypt(byte[] plainBytes, string passPhrase)
{
    byte[] keyBytes = GetKeyBytes(passPhrase);

    using (RijndaelManaged symmetricKey = new RijndaelManaged())
    {
        symmetricKey.GenerateIV();
        symmetricKey.Mode = CipherMode.CBC;

        using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, symmetricKey.IV))
        using (MemoryStream memoryStream = new MemoryStream())
        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(plainBytes, 0, plainBytes.Length);
            cryptoStream.FlushFinalBlock();
            return Combine(memoryStream.ToArray(), symmetricKey.IV);
        }
    }
} 

If we want to decrypt this array, we need to get the last 16 bytes and use it as IV vector. The bytes till the last 16 bytes will then be the encrypted data itself.

Now, let us use the Encrypt() method

Console.Write("Enter File Path: ");
docPath = Console.ReadLine();
byte[] binarydata = File.ReadAllBytes(docPath);
byte[] Encrypted = Encrypt(binarydata, m.ToString());
File.WriteAllBytes(docPath + ".aent", Encrypted);

Additional

You really should check out the System.IO.Path class, as this class provides many helpful methods like GetExtension() for getting the fileextension, GetFileNameWithoutExtension() etc. .

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.