9

I'm having some trouble with the code below. I have a file in a temporary location which is in need of encryption, this function encrypts that data which is then stored at the "pathToSave" location.

On inspection is does not seem to be handling the whole file properly - There are bits missing from my output and I suspect it has something to do with the while loop not running through the whole stream.

As an aside, if I try and call CryptStrm.Close() after the while loop I receive an exception. This means that if I attempt to decrypt the file, I get a file already in use error!

Tried all the usual and Ive looked on here at similar issues, any help would be great.

Thanks

public void EncryptFile(String tempPath, String pathToSave)
    {
        try
        {
            FileStream InputFile = new FileStream(tempPath, FileMode.Open, FileAccess.Read);
            FileStream OutputFile = new FileStream(pathToSave, FileMode.Create, FileAccess.Write);

            RijndaelManaged RijCrypto = new RijndaelManaged();

            //Key
            byte[] Key = new byte[32] { ... };

            //Initialisation Vector
            byte[] IV = new byte[32] { ... };

            RijCrypto.Padding = PaddingMode.None;
            RijCrypto.KeySize = 256;
            RijCrypto.BlockSize = 256;
            RijCrypto.Key = Key;
            RijCrypto.IV = IV;

            ICryptoTransform Encryptor = RijCrypto.CreateEncryptor(Key, IV);

            CryptoStream CryptStrm = new CryptoStream(OutputFile, Encryptor, CryptoStreamMode.Write);

            int data;
            while (-1 != (data = InputFile.ReadByte()))
            {
                CryptStrm.WriteByte((byte)data);
            }
        }
        catch (Exception EncEx)
        {
            throw new Exception("Encoding Error: " + EncEx.Message);
        }
    }

EDIT:

I've made the assumption that my problem is with Encryption. My Decrypt might be the culprit

        public String DecryptFile(String encryptedFilePath)
    {
        FileStream InputFile = new FileStream(encryptedFilePath, FileMode.Open, FileAccess.Read);

        RijndaelManaged RijCrypto = new RijndaelManaged();

        //Key
        byte[] Key = new byte[32] { ... };

        //Initialisation Vector
        byte[] IV = new byte[32] { ... };

        RijCrypto.Padding = PaddingMode.None;
        RijCrypto.KeySize = 256;
        RijCrypto.BlockSize = 256;
        RijCrypto.Key = Key;
        RijCrypto.IV = IV;

        ICryptoTransform Decryptor = RijCrypto.CreateDecryptor(Key, IV);

        CryptoStream CryptStrm = new CryptoStream(InputFile, Decryptor, CryptoStreamMode.Read);

        String OutputFilePath = Path.GetTempPath() + "myfile.name";
        StreamWriter OutputFile = new StreamWriter(OutputFilePath);

        OutputFile.Write(new StreamReader(CryptStrm).ReadToEnd());

        CryptStrm.Close();
        OutputFile.Close();

        return OutputFilePath;
    }
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
David
  • 125
  • 1
  • 2
  • 7

3 Answers3

18

Well, there are several things going wrong here.

1. Your setting the size of the IV too large. If you have a key size of 256, the IV for Rijndael is 16 bytes. If you try to set the key with more bytes than that you will get an exception. IV size should be set to Block size / 8 see MSDN so the IV size you have is correct.

  1. You have a padding mode of None. Unless your data is exactly the correct block size, then you are going to have problems. You probably want to pick a padding mode.
  2. You typically need to call FlushFInalBlock when done writing to the crypto stream when you are encrypting to ensure all of the data is written to the stream.

I am posting some sample code using memory streams to show the encrypting and decrypting.

var tempData = "This is the text to encrypt. It's not much, but it's all there is.";

using (var rijCrypto = new RijndaelManaged())
{
    byte[] encryptedData;
    rijCrypto.Padding = System.Security.Cryptography.PaddingMode.ISO10126;
    rijCrypto.KeySize = 256;

    using (var input = new MemoryStream(Encoding.Unicode.GetBytes(tempData)))
    using (var output = new MemoryStream())
    {
        var encryptor = rijCrypto.CreateEncryptor();

        using (var cryptStream = new CryptoStream(output, encryptor, CryptoStreamMode.Write))
        {
            var buffer = new byte[1024];
            var read = input.Read(buffer, 0, buffer.Length);
            while (read > 0)
            {
                cryptStream.Write(buffer, 0, read);
                read = input.Read(buffer, 0, buffer.Length);
            }
            cryptStream.FlushFinalBlock();
            encryptedData = output.ToArray();
        }
    }

    using (var input = new MemoryStream(encryptedData))
    using (var output = new MemoryStream())
    {
        var decryptor = rijCrypto.CreateDecryptor();
        using (var cryptStream = new CryptoStream(input, decryptor, CryptoStreamMode.Read))
        {
            var buffer = new byte[1024];
            var read = cryptStream.Read(buffer, 0, buffer.Length);
            while (read > 0)
            {
                 output.Write(buffer, 0, read);
                 read = cryptStream.Read(buffer, 0, buffer.Length);
            }
            cryptStream.Flush();
            var result = Encoding.Unicode.GetString(output.ToArray());
        }
    }
}
pstrjds
  • 16,840
  • 6
  • 52
  • 61
  • 1
    Thanks pstrjds - I've made some changes but still having issues. IV is now 16 bytes, padding is Zeros and I've changed my while loop to look more like your suggestion but I'm still getting erros when I call FlushFinalBlock - "Method was called twice on CryptoStream. It can only be called once". I dont trust that it has been called in the first place though! – David Mar 16 '12 at 09:15
  • Well, you should only call FlushFinalBlock outside of the loop as the last thing when you are encrypting. You don't need to call it when you are decrypting, when decrypting just call Flush. Your block size should be correct when you are decrypting so there won't need to be any padding added. As far as trusting whether it has been called, you can run your code under a debugger and set a break point at that line. If it doesn't hit it, then it wasn't called. – pstrjds Mar 16 '12 at 12:48
  • 1
    _If you have a key size of 256, the IV for Rijndael is 16 bytes. If you try to set the key with more bytes than that you will get an exception._ This is incorrect, if you have a `BlockSize = 256` then your IV should be 32. If they are not properly match then you will get an exception. – Trisped Mar 02 '13 at 05:00
  • @Trisped - good catch. I fixed it and added a link to documentation on setting the BlockSize/IV size ratio. – pstrjds Mar 04 '13 at 06:05
  • This code doesn't even appear to work. I was expecting to see the original string come out the end, but when I run it, the decrypted string looks like a lot of Asian characters. Why? – Obi Wan Nov 14 '17 at 19:09
  • @ObiWan - Are you sure you copied the code and used it as it is? I just retested it and it worked just fine for me. The `result` string contained the decrypted original string. The one change I would probably make to the code now, is I would encode as UTF8 instead of as Unicode. It's not formatted well, but here is an [Ideone](https://ideone.com/QjWcPi) link to the code, showing it working. – pstrjds Nov 14 '17 at 20:14
  • .Net 4.7 this code gives this message: Message: System.Security.Cryptography.CryptographicException : Padding is invalid and cannot be removed. – Richard Griffiths Nov 24 '17 at 15:12
  • @RichardGriffiths - I just ran this code under .Net version 4.7 and did not get any exceptions. – pstrjds Nov 24 '17 at 16:23
  • I'm going to post a separate question as I'm coming from VB and I've found most questions on this so far, got no closer. It's very puzzling. – Richard Griffiths Nov 24 '17 at 16:41
  • Don't know if this is bad manners to link to my question here so apologies in advance! https://stackoverflow.com/questions/47477421/padding-is-invalid-and-cannot-be-removed-aes-yet-another – Richard Griffiths Nov 24 '17 at 17:04
  • 1
    @RichardGriffiths - I have [answered](https://stackoverflow.com/a/47477966/416574) your question with a working sample. – pstrjds Nov 24 '17 at 17:43
  • Thank you, having a look just before I go home now. – Richard Griffiths Nov 24 '17 at 17:44
  • @pstrjds - This code works great, but if the code is chopped into two, so that you have using (var rijCrypto = new RijndaelManaged()) for encrypting (and you store key and IV from rijCrypto in variables) and then another using (var rijCrypto = new RijndaelManaged()) for decryption, setting the key and IV, I get the padding exception. What else needs to be maintained to decrypt correctly? – DomBat Feb 07 '18 at 16:14
  • @pstrjds - I've just done some more debugging. I got it to work into two pieces just by using the default padding (PKCS7) rather than ISO10126 as specified in the code. Is there a reason to choose ISO10126? Thanks for the code though, works great thanks! – DomBat Feb 07 '18 at 16:42
  • 1
    @DomBat - [Ideone link](https://ideone.com/iLzDIs) to a working sample where you use a separate `RijndaelManaged` object for encrypting and decrypting (saving the IV and key from encryption and using the saved IV and key for decryption). No padding exceptions, all works fine. I chose ISO10126 because I prefer padding with random data. – pstrjds Feb 08 '18 at 06:39
7

Another way to read CryptoStream is to using CopyTo methord:

...

    using (var output = new MemoryStream())
    {
       cryptStream.CopyTo(output);
       return output.ToArray();
    }

...
SLdragon
  • 1,477
  • 16
  • 19
-1

The KeySize must be specified first. This looks like a bug e.g. this works

            using (var aes = new AesManaged())
            {
                aes.KeySize = 256;
                aes.Mode = CipherMode.CBC;
                aes.IV = iv;
                aes.Key = passwordHash;
                aes.Padding = PaddingMode.PKCS7;

but this doesn't

            using (var aes = new AesManaged())
            {
                aes.Mode = CipherMode.CBC;
                aes.IV = iv;
                aes.Key = passwordHash;
                aes.Padding = PaddingMode.PKCS7;
                aes.KeySize = 256;
Simon Smith
  • 226
  • 3
  • 8