Why can't I combine encryption with file storage, when they work individually?

362 Views Asked by At

Why can't I combine encryption with file storage, when they work individually?

I'm writing some object properties to a file. First I convert properties to byte array and then I put the whole array (for "one object") together and encrypt it via MemoryStream using Aes. I know about serialization and other possibilities but I really need to do it this way. In some other method I read this file in chunks ("objects"), decrypt it and then reconstruct object properties from byte array. The thing is that only the very first record ("object") gets decrypted and reconstructed normally / correctly. All others get messed up data (int gets value 48464 instead of 2, String showing odd signs, double is -3.16...E-161 instead of 20...).

And I have no idea why. I tried everything I could possibly think of. If I comment out the encryption and decryption everything works, so it is not a problem with writing & reading. If I place the code for decryption and reconstruction of objects just below the code for encryption (so that I decrypt the data chunk which gets written) it decrypts and reconstructs everything properly, thus it shouldn't be a problem with decryption and reconstruction. But when it is all together it just messes up. I'm really lost here.

Please don't focus on the way I manipulate with the data it's really not important right now and I have my reasons why I do it this way.

Here is the whole code for saving to file:

//constant for setting inUse
byte setInUse = 0x80; //1000 0000

//constant for adding spaces to name (string)
byte[] space = Encoding.UTF8.GetBytes(" ");

//result
byte[] data = new byte[32];

//setup encryption (AES)
SymmetricAlgorithm aes = Aes.Create();
byte[] key = { 145, 12, 32, 245, 98, 132, 98, 214, 6, 77, 131, 44, 221, 3, 9, 50 };
byte[] iv = { 15, 122, 132, 5, 93, 198, 44, 31, 9, 39, 241, 49, 250, 188, 80, 7 };
aes.Padding = PaddingMode.None;
ICryptoTransform encryptor = aes.CreateEncryptor(key, iv);        
            
//setup file stream for saving data
FileStream fStream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 1024, false);
if(writeIndex != 0)
    fStream.Position = writeIndex +1;

fStream.Position = 0; //delete me

foreach(Article article in articles)
{
   if(article.MyIsNew)
   {
       article.MyInUseChanged = false;
       article.MyPriceChanged = false;

       //convert article to byte array
       //id
       byte[] id = BitConverter.GetBytes(Convert.ToUInt16(article.MyId));   
       //in use
       if (article.MyInUse)
           id[0] = (byte)( id[0] | setInUse);

       data[0] = id[0];
       data[1] = id[1];

       //stock
       byte[] stock = BitConverter.GetBytes(article.MyStock);
       data[2] = stock[0];
       data[3] = stock[1];
       data[4] = stock[2];
       data[5] = stock[3];
       data[6] = stock[4];
       data[7] = stock[5];
       data[8] = stock[6];
       data[9] = stock[7];

       //name
       byte[] name = Encoding.UTF8.GetBytes(article.MyName);
       int counter = 10;
       for (int i = 0; i < name.Length; i++)
       {
           data[counter] = name[i];
           counter++;
       }

       //adding spaces
       int numToAdd = 22-name.Length;
       for (int i = 0; i < numToAdd; i++)
       {
           data[counter] = space[0];
       }

       //encrypt
       MemoryStream m = new MemoryStream();
       using (Stream c = new CryptoStream(m, encryptor, CryptoStreamMode.Write))
           c.Write(data, 0, data.Length);
       byte[] original = new byte[32];
       original = m.ToArray();
       fStream.Write(original, 0, original.Length);

   }
   else if (article.MyInUseChanged)
   {

   }

   if (article.MyPriceChanged)
   {

   }

}   
fStream.Flush();
fStream.Close();
fStream.Dispose();

And here is the whole code for loading:

String fileName = path + "\\articles";

//load data
if (File.Exists(fileName))
{
    FileStream fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
    
    //setup encryption (AES)
    SymmetricAlgorithm aes = Aes.Create();
    byte[] key = { 145, 12, 32, 245, 98, 132, 98, 214, 6, 77, 131, 44, 221, 3, 9, 50 };
    byte[] iv = { 15, 122, 132, 5, 93, 198, 44, 31, 9, 39, 241, 49, 250, 188, 80, 7 };
    aes.Padding = PaddingMode.None;
    ICryptoTransform decryptor = aes.CreateDecryptor(key, iv);
                    
    //constant for extracting inUse
    byte inUseConst = 0x80;

    //constant for extracting id
    byte idConst = 0x7F;

    byte[] idArray = new byte[2];

    //reading & constructing & adding articles to the list
    int numBytesToRead = (int)fStream.Length;
    while (numBytesToRead > 0)
    {
        byte[] original = new byte[32];
        byte[] data = new byte[32];

        int len = fStream.Read(original, 0, 32);
        numBytesToRead -= 32;
        if (len == 0 || len != 32)
        {
            MessageBox.Show("Error while loading articles");
            break;
        }
        long pos = fStream.Position; //delete me
        //decrypt
        MemoryStream m = new MemoryStream();
        using (Stream c = new CryptoStream(m, decryptor, CryptoStreamMode.Write))
            c.Write(original, 0, original.Length);
        data = m.ToArray();

        //constructing object - article
        //inUse
        byte inUseCalc = (byte)(data[0] & inUseConst);
        bool inUse = false;
        if (inUseCalc != 0)
        {
            inUse = true;
        }

        //id
        data[0] = (byte)(data[0] & idConst);
        int id = (int)(BitConverter.ToUInt16(data, 0));

        //stock
        double stock = BitConverter.ToDouble(data, 2);

        //name
        String name = Encoding.UTF8.GetString(data, 10, 22);

        Article article = new Article(id, 10, name, inUse, stock);
        articles.Add(article);

Some things are not optimal, because I changed a lot just to try to find the solution. Some things (like converting to uInt16 and using 'or' and alike) are partially because of compression.

Please help me solve this and again please don't focus on my handling with the data or advising me to use serialization or binary writer or similar, I really have my reasons.

2

There are 2 best solutions below

1
On BEST ANSWER

The actual problem is that you are re-creating the output CryptoStream each time. Apparently doing this is modifying some state in the encryptor which causes the first few bytes of each record to be output differently to how the decryptor is expecting it.

If you construct the encrypting CryptoStream outside of the loop, either writing directly to the output file or to a single MemoryStream, the problem goes away (and I actually tested it this time...)

using (var fStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
using (var m = new MemoryStream())
using (var c = new CryptoStream(m, encryptor, CryptoStreamMode.Write))
{
    foreach (Article article in articles)
    {
        // ...
        c.Write(data, 0, data.Length);
        byte[] original = new byte[32];
        original = m.ToArray();
        m.Position = 0;
        fStream.Write(original, 0, original.Length);
    }
}
4
On

CryptoStream is used identically to any other stream, that is, if you wrap it around a FileStream and cast it back to Stream, you can't tell the difference.

In you decryption code, you are writing to the CryptoStream rather than reading from it. You probably want something more like this:

using (Stream c = new CryptoStream(fStream, decryptor, CryptoStreamMode.Read))
{
    while (numBytesToRead > 0)
    {
        byte[] original = new byte[32];
        byte[] data = new byte[32];

        int len = c.Read(original, 0, 32);
        numBytesToRead -= 32;

        // and so on
    }
}

(As requested, I'm not passing comment on your general approach. However, I would suggest that if possible you read the entire CryptoStream into memory (write it immediately into a MemoryStream) and close the original file and stream objects as soon as possible.)