views:

89

answers:

1

I have the need to calculate the size of a file I am encrypting using Rijndael.

According to other answers on this site, and on google, the following is the correct way of calculating encrypted data length:

EL = UL + (BS - (UL Mod BS) Mod BS)

Where: 
EL = Encrypted Length
UL = Unencrypted Length
BS = Block Size

In my instance, the unencrypted file length is 5,101,972 bytes, and I am using a 128bit encryption key, giving me a block size of 16 bytes. Therefore, the equation is:

EL = 5101972 + (16 - (5101972 Mod 16) Mod 16)
EL = 5101972 + (16 - 4 Mod 16)
EL = 5101972 + (12 Mod 16)
EL = 5101972 + 12
EL = 5101984

Giving an encrypted file length of 5,101,984 bytes.

However, the size of my file after encryption weighs in at 5,242,896 A massive difference in sizes of 140,912 bytes!

Now.. I'm obviously doing SOMETHING wrong, but I can't work out what it is. Below is my encryption and decryption test code, as well as the method used to calculate the encrypted size:

private static void Enc(string decryptedFileName, string encryptedFileName)
{
    PasswordDeriveBytes passwordDB = new PasswordDeriveBytes("ThisIsMyPassword", Encoding.ASCII.GetBytes("thisIsMysalt!"), "MD5", 2);
    byte[] passwordBytes = passwordDB.GetBytes(128 / 8);

    using (FileStream fsOutput = File.OpenWrite(encryptedFileName))
    {
        using(FileStream fsInput = File.OpenRead(decryptedFileName))
        {
            byte[] IVBytes = Encoding.ASCII.GetBytes("1234567890123456");

            fsOutput.Write(BitConverter.GetBytes(fsInput.Length), 0, 8);
            fsOutput.Write(IVBytes, 0, 16);

            RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.Zeros};
            ICryptoTransform encryptor = symmetricKey.CreateEncryptor(passwordBytes, IVBytes);

            using (CryptoStream cryptoStream = new CryptoStream(fsOutput, encryptor, CryptoStreamMode.Write))
            {
                for (long i = 0; i < fsInput.Length; i += chunkSize)
                {
                    byte[] chunkData = new byte[chunkSize];
                    int bytesRead = 0;
                    while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
                    {
                        cryptoStream.Write(chunkData, 0, chunkSize);
                    }
                }
            }
        }
    }            
}

private static void Dec(string encryptedFileName, string decryptedFileName)
{
    PasswordDeriveBytes passwordDB = new PasswordDeriveBytes("ThisIsMyPassword", Encoding.ASCII.GetBytes("thisIsMysalt!"), "MD5", 2);
    byte[] passwordBytes = passwordDB.GetBytes(128 / 8);

    using (FileStream fsInput = File.OpenRead(encryptedFileName))
    {
        using (FileStream fsOutput = File.OpenWrite(decryptedFileName))
        {
            byte[] buffer = new byte[8];
            fsInput.Read(buffer, 0, 8);

            long fileLength = BitConverter.ToInt64(buffer, 0);

            byte[] IVBytes = new byte[16];
            fsInput.Read(IVBytes, 0, 16);


            RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.Zeros};
            ICryptoTransform decryptor = symmetricKey.CreateDecryptor(passwordBytes, IVBytes);

            using (CryptoStream cryptoStream = new CryptoStream(fsOutput, decryptor, CryptoStreamMode.Write))
            {
                for (long i = 0; i < fsInput.Length; i += chunkSize)
                {
                    byte[] chunkData = new byte[chunkSize];
                    int bytesRead = 0;
                    while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
                    {
                        cryptoStream.Write(chunkData, 0, bytesRead);
                    }
                }
                fsOutput.SetLength(fileLength);
            }                    
        }
    }
}

private static void CalcEncSize(string decryptedFileName)
{
    FileInfo fi = new FileInfo(decryptedFileName);
    if (fi.Exists)
    {                
        long blockSize = 128/8;
        long fileLength = fi.Length;
        long encryptedSize = fileLength + ((blockSize - (fileLength % blockSize)) % blockSize);
        encryptedSize += 24; //16 bytes for the IV, and 8 more for the filelength, both stored at the start of the file.

        Console.WriteLine("Estimated Encryption Size: " + encryptedSize.ToString());           
    }
}

Note: In the calculations at the start, I am NOT including the extra 24 bytes that are used at the start of the encrypted file by myself to store the original file length, and the IV... I know this, but didn't want to complicate the equation more than necessary.

+3  A: 

You shouldn't write to the output file before the actual encryption process. The CryptoStream will handle all the necessary padding when it is closed. Also the for loop isn't necessary as the inner while loop will read the entire file. Also you should only write as much as was read from the file. Try these changes.

private static void Enc(string decryptedFileName, string encryptedFileName)
{
    PasswordDeriveBytes passwordDB = new PasswordDeriveBytes("ThisIsMyPassword", Encoding.ASCII.GetBytes("thisIsMysalt!"), "MD5", 2);
    byte[] passwordBytes = passwordDB.GetBytes(128 / 8);

    using (FileStream fsOutput = File.OpenWrite(encryptedFileName))
    {
        using(FileStream fsInput = File.OpenRead(decryptedFileName))
        {
            byte[] IVBytes = Encoding.ASCII.GetBytes("1234567890123456");

            RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.Zeros};
            ICryptoTransform encryptor = symmetricKey.CreateEncryptor(passwordBytes, IVBytes);

            using (CryptoStream cryptoStream = new CryptoStream(fsOutput, encryptor, CryptoStreamMode.Write))
            {
                byte[] chunkData = new byte[chunkSize];
                int bytesRead = 0;
                while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
                {
                    cryptoStream.Write(chunkData, 0, bytesRead);  //KEY FIX
                }
            }
        }
    }            
}

[edit]

Oh I've missed out on a lot of information. I misread what your sizes were thinking 140,912 was the size, not difference. Now that I see that, I can make more intelligible response. Based on your code, the difference in sizes should be comparable to your chunk size. Since the chunkSize can be somewhat large, your code will typically write up to chunkSize more data than is actually in your input file (as Greg and caf pointed out). The line that I've marked is the reason for the discrepancy.

Jeff M
Can you explain why I cant write to the stream before the encryption?The data I'm writing right at the start (the first 24 bytes) doesn't want to be encrypted, as I need it to unencrypt the rest.It actually doesn't seem make any difference if I use chunkSize, or bytesRead, as any unused bytes in the chuckData array are just null, and don't seem to get encoded by the cryptoStream.Write() method anyway.If this wasn't the case, wouldn't this surely negatively affect encryption / decryption process, which works perfectly fine?
Sk93
As for writing to the file before the encryption, I suppose that would be fine. Just that you need to remember that if you try to decrypt, you need to offset past these written bytes to properly decrypt the data. It really shouldn't affect the encryption process but does the decryption. I'll update my answer to directly address what seems to cause the problem.
Jeff M
Sorry, I'll have to think about it. I thought I would have a reasonable explanation but don't at the moment (and I'm pressed for time). I've helped another person with pretty much the same exact problem but never heard back on the final result. I'll get back to you on this.
Jeff M
You were correct that those changes shouldn't affect the process I would think. The only other possible explanation I could think of is that the `CryptoStream` is being closed before it could finish writing to the output file. Is the "encrypted" file length consistently the same every time you try to encrypt the same file? My personal tests shows that it gives reasonable file sizes for the files I've tested.
Jeff M
You're code correctly uses the bytesRead and the OPs doesn't. That is a serious bug in the OP's code. All chunksize bytes are encrypted everytime through the loop, no matter how many bytes were actually read in.
GregS
GregS - see my initial comment. I don't believe it to be that serious at all... It certainly has no bearing on this issue.
Sk93
@Sk93: @GregS and Jeff M are correct - in your code those extra 0 bytes will be happily encrypted and written as if they were part of the original unencrypted data - the `CryptoStream` doesn't know the difference. This is why your file is expanding so much.
caf
Ah I totally misunderstood problem in the question. Updated my answer which explains it. (and would be nice to know the reasoning behind the downvote)
Jeff M