views:

640

answers:

2

I am in the process of rewriting an old application. The old app stored data in a scoreboard file that was encrypted with the following code:

private const String SSecretKey = @"?B?n?Mj?";
public DataTable GetScoreboardFromFile()
{
    FileInfo f = new FileInfo(scoreBoardLocation);
    if (!f.Exists)
    {
        return setupNewScoreBoard();
    }

    DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
    //A 64 bit key and IV is required for this provider.
    //Set secret key For DES algorithm.
    DES.Key = ASCIIEncoding.ASCII.GetBytes(SSecretKey);
    //Set initialization vector.
    DES.IV = ASCIIEncoding.ASCII.GetBytes(SSecretKey);

    //Create a file stream to read the encrypted file back.
    FileStream fsread = new FileStream(scoreBoardLocation, FileMode.Open, FileAccess.Read);
    //Create a DES decryptor from the DES instance.
    ICryptoTransform desdecrypt = DES.CreateDecryptor();
    //Create crypto stream set to read and do a 
    //DES decryption transform on incoming bytes.
    CryptoStream cryptostreamDecr = new CryptoStream(fsread, desdecrypt, CryptoStreamMode.Read);

    DataTable dTable = new DataTable("scoreboard");
    dTable.ReadXml(new StreamReader(cryptostreamDecr));

    cryptostreamDecr.Close();
    fsread.Close();

    return dTable;
}

This works fine. I have copied the code into my new app so that I can create a legacy loader and convert the data into the new format. The problem is I get a "Bad Data" error:

System.Security.Cryptography.CryptographicException was unhandled Message="Bad Data.\r\n" Source="mscorlib"

The error fires at this line:

dTable.ReadXml(new StreamReader(cryptostreamDecr));

The encrypted file was created today on the same machine with the old code. I guess that maybe the encryption / decryption process uses the application name / file or something and therefore means I can not open it.

Does anyone have an idea as to:

A) Be able explain why this isn't working? B) Offer a solution that would allow me to be able to open files that were created with the legacy application and be able to convert them please?

Here is the whole class that deals with loading and saving the scoreboard:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.IO;
using System.Data;
using System.Xml;
using System.Threading;

namespace JawBreaker
{
[Serializable]
class ScoreBoardLoader
{
    private Jawbreaker jawbreaker;
    private String sSecretKey = @"?B?n?Mj?";
    private String scoreBoardFileLocation = "";
    private bool keepScoreBoardUpdated = true;
    private int intTimer = 180000;

    public ScoreBoardLoader(Jawbreaker jawbreaker, String scoreBoardFileLocation)
    {
        this.jawbreaker = jawbreaker;
        this.scoreBoardFileLocation = scoreBoardFileLocation;
    }

    //  Call this function to remove the key from memory after use for security
    [System.Runtime.InteropServices.DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
    public static extern bool ZeroMemory(IntPtr Destination, int Length);

    // Function to Generate a 64 bits Key.
    private string GenerateKey()
    {
        // Create an instance of Symetric Algorithm. Key and IV is generated automatically.
        DESCryptoServiceProvider desCrypto = (DESCryptoServiceProvider)DESCryptoServiceProvider.Create();

        // Use the Automatically generated key for Encryption. 
        return ASCIIEncoding.ASCII.GetString(desCrypto.Key);
    }

    public void writeScoreboardToFile()
    {
        DataTable tempScoreBoard = getScoreboardFromFile();
        //add in the new scores to the end of the file.
        for (int i = 0; i < jawbreaker.Scoreboard.Rows.Count; i++)
        {
            DataRow row = tempScoreBoard.NewRow();
            row.ItemArray = jawbreaker.Scoreboard.Rows[i].ItemArray;
            tempScoreBoard.Rows.Add(row);
        }

        //before it is written back to the file make sure we update the sync info
        if (jawbreaker.SyncScoreboard)
        {
            //connect to webservice, login and update all the scores that have not been synced.

            for (int i = 0; i < tempScoreBoard.Rows.Count; i++)
            {
                try
                {
                    //check to see if that row has been synced to the server
                    if (!Boolean.Parse(tempScoreBoard.Rows[i].ItemArray[7].ToString()))
                    {
                        //sync info to server

                        //update the row to say that it has been updated
                        object[] tempArray = tempScoreBoard.Rows[i].ItemArray;
                        tempArray[7] = true;
                        tempScoreBoard.Rows[i].ItemArray = tempArray;
                        tempScoreBoard.AcceptChanges();
                    }
                }
                catch (Exception ex)
                {
                    jawbreaker.writeErrorToLog("ERROR OCCURED DURING SYNC TO SERVER UPDATE: " + ex.Message);
                }
            }
        }

        FileStream fsEncrypted = new FileStream(scoreBoardFileLocation, FileMode.Create, FileAccess.Write);
        DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
        DES.Key = ASCIIEncoding.ASCII.GetBytes(sSecretKey);
        DES.IV = ASCIIEncoding.ASCII.GetBytes(sSecretKey);
        ICryptoTransform desencrypt = DES.CreateEncryptor();
        CryptoStream cryptostream = new CryptoStream(fsEncrypted, desencrypt, CryptoStreamMode.Write);

        MemoryStream ms = new MemoryStream();
        tempScoreBoard.WriteXml(ms, XmlWriteMode.WriteSchema);

        ms.Position = 0;

        byte[] bitarray = new byte[ms.Length];
        ms.Read(bitarray, 0, bitarray.Length);

        cryptostream.Write(bitarray, 0, bitarray.Length);
        cryptostream.Close();
        ms.Close();

        //now the scores have been added to the file remove them from the datatable
        jawbreaker.Scoreboard.Rows.Clear();
    }

    public void startPeriodicScoreboardWriteToFile()
    {
        while (keepScoreBoardUpdated)
        {
            //three minute sleep.
            Thread.Sleep(intTimer);
            writeScoreboardToFile();
        }
    }

    public void stopPeriodicScoreboardWriteToFile()
    {
        keepScoreBoardUpdated = false;
    }

    public int IntTimer
    {
        get
        {
            return intTimer;
        }
        set
        {
            intTimer = value;
        }
    }

    public DataTable getScoreboardFromFile()
    {
        FileInfo f = new FileInfo(scoreBoardFileLocation);
        if (!f.Exists)
        {
            jawbreaker.writeInfoToLog("Scoreboard not there so creating new one");
            return setupNewScoreBoard();
        }
        else
        {
            DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
            //A 64 bit key and IV is required for this provider.
            //Set secret key For DES algorithm.
            DES.Key = ASCIIEncoding.ASCII.GetBytes(sSecretKey);
            //Set initialization vector.
            DES.IV = ASCIIEncoding.ASCII.GetBytes(sSecretKey);

            //Create a file stream to read the encrypted file back.
            FileStream fsread = new FileStream(scoreBoardFileLocation, FileMode.Open, FileAccess.Read);
            //Create a DES decryptor from the DES instance.
            ICryptoTransform desdecrypt = DES.CreateDecryptor();
            //Create crypto stream set to read and do a 
            //DES decryption transform on incoming bytes.
            CryptoStream cryptostreamDecr = new CryptoStream(fsread, desdecrypt, CryptoStreamMode.Read);

            DataTable dTable = new DataTable("scoreboard");
            dTable.ReadXml(new StreamReader(cryptostreamDecr));

            cryptostreamDecr.Close();
            fsread.Close();

            return dTable;
        }
    }

    public DataTable setupNewScoreBoard()
    {
        //scoreboard info into dataset
        DataTable scoreboard = new DataTable("scoreboard");
        scoreboard.Columns.Add(new DataColumn("playername", System.Type.GetType("System.String")));
        scoreboard.Columns.Add(new DataColumn("score", System.Type.GetType("System.Int32")));
        scoreboard.Columns.Add(new DataColumn("ballnumber", System.Type.GetType("System.Int32")));
        scoreboard.Columns.Add(new DataColumn("xsize", System.Type.GetType("System.Int32")));
        scoreboard.Columns.Add(new DataColumn("ysize", System.Type.GetType("System.Int32")));
        scoreboard.Columns.Add(new DataColumn("gametype", System.Type.GetType("System.String")));
        scoreboard.Columns.Add(new DataColumn("date", System.Type.GetType("System.DateTime")));
        scoreboard.Columns.Add(new DataColumn("synced", System.Type.GetType("System.Boolean")));

        scoreboard.AcceptChanges();
        return scoreboard;
    }

    private void Run()
    {
        // For additional security Pin the key.
        GCHandle gch = GCHandle.Alloc(sSecretKey, GCHandleType.Pinned);

        // Remove the Key from memory. 
        ZeroMemory(gch.AddrOfPinnedObject(), sSecretKey.Length * 2);
        gch.Free();
    }
}
}
A: 

You might want to check this link out: http://blogs.msdn.com/shawnfa/archive/2005/11/10/491431.aspx

It talks about problems with encryption/decryption and ascii encoding.

Chris Lively
I maybe misunderstanding the article but I don't think thats my problem here. although it could become an issue in the future, multi lingual stuff for example. The legacy app can open the file no problem, I have literally copied the code, (cut and paste) it into the new app and run it. If the old app has no issues I am uncertain why the new one would.
Jon
If the old app is written in .net 1.1 and the new one is in .net 2.0 or higher, then the ascii encoding is going to be a big problem. Mainly because they "fixed" it in the 2.0 framework...
Chris Lively
So you need to get rid of all the ASCII encoding stuff
Chris Lively
They are both written in 2.0, (well actually the new one is probably targeting 3.5). Will have a go at rewriting it and see what happens, thanks :)
Jon
+2  A: 

That looks like code to decrypt a file and deserialise your scoreboard object from it. We really need to see the original encryption side to compare, if you can post that.

I have two observations from the decryption code:

  • Setting the IV the same as the key doesn't look right (though it might be correct if that's what your encryption code is doing)
  • Are those really question marks in SSecretKey? Could they be control or high-byte-set characters fallen victim to mis-encoding and/or mispasting?

Added: Thanks for the update. A bit of googling suggests that Bad Data is often caused by the decrypted data not being exactly what was output from the encryption, that's a place to focus (but do keep looking into whether the key string is correct). I must admit I'm a little out of my depth in C#, so the following are only suggestions:

  • In the writeScoreboardToFile you write the XML to a MemoryStream, then write the contents of that stream to your CryptoStream. Is there any reason why you don't write directly to the CryptoStream?
  • Is it legal to set ms.Position directly? Does ms.Seek(0,SeekOrigin.Begin) make any difference?
  • You may need to call cryptostream.FlushFinalBlock() before closing it, it's not clear from MSDN whether it's automatically called.
  • The encrypted data is binary, you may need to use a BinaryReader and BinaryWriter to avoid it getting smashed by the encodings, as Chris pointed out.
  • When exactly is Run() being called? If I've understood correctly, that is zeroing out the secret key string - obviously you don't want this to happen before you're finished creating your crypto transforms.

On top of this I'd comment that setting the IV equal to the key is not very sound cryptographically; the IV should really be different every time you encrypt using one key.

crazyscot
Hi, thanks for the input. I have added the full code for you. Yep I think they were question marks. I think, will have to go back to the old stuff to check.
Jon