Ok...I spent hours (I know, probably something you pros know already) trying to figure this out. I finally did and thought I would share it with others considering I found nothing (literally) out there on this.

Feedback would be much appreciated!

Most of the time string data written to a file using the BinaryFormatter and a FileStream is human readable (to a point). You may not be able to directly tell what the string is (like is it a username, password whatever) but the string data itself is left as is. This is a problem if the data you are serializing is sensitive, or contains usernames or passwords for servers or other settings.

I figured out a way to serialize data and encrypt it all in one step. It's not perfect, but it works well for me.

Check this out:

I created a class that holds a few pieces of information, one of them is a connection string to an SQL server. (the data is fake I was just testing it). I called a method to serialize the entire class to a file. I opened the file that was created using Notepad and this is what was in it:

ÿÿÿÿ ;DATA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null DATA.LocalSettings BalloonHelpSysRunNumberSysFirstRundbmsConnectionTypedbmsDataSourcedbmsUserNamedbmsPassworddbmsCatalogdbmsIntegratedSecuritydbmsPersistSecurityconnSql ÿÿÿÿ


dData Source=localhost;Initial Catalog=master;Integrated Security=True;User ID=Zach;Password=MyPaSsWoRd

Not so good. The connection string is pretty obvious if it's what you are looking for.

I changed my method a little to put the class data into a MemoryStream first and then serialized the MemoryString to a file. This is what it looked like in Notepad:

ÿÿÿÿ System.IO.MemoryStream
_buffer_origin _position_length _capacity_expandable _writable
_exposable_isOpenMarshalByRefObject+__identity ' '
¶Eÿ}­/iOÀzŸŒŽsR­‡Ebä/3ÅMï“3`)”?m`€ƒ
×°d‹µñ†/H$çhÃêêÍGa @ν²C"ù[5ñß¡Æ<‹ö¤hÇš‘ÕŒA쥀!÷í‰íÉëŠGŪàŸäÙ§HLKEè&º¨UÊRovþ¬ƒZONë=ÀT0{ùò9ÀrûI9\4Œýu°k&Ù¡=Ôc¢W&†\Âës‚—˜P+$ÏÆ´0&Æ‘Í·Þ
Ú‡9¥ƒÔ´¡n
¸-Gekíß•¢ñêëñf—öhúžœ ë¶Q÷8q’¬;…yDïýú8ž›ëzÒ¼N‹$9t ‡Ä=N>D *ØÔ—?93¶¼Ó!Ë„ƒëHõÖô˜ifÒp3jÎXÙ3žÃñã.Ô¦úÞÕ‚3—PÜÃÍ׬¼_2Œÿªiì!´ýTqÁ“\Ô•({1¤ˆ‚¿¦ÖÛoÆÊ`$ÈK‰B8“âm÷ĵ†´UHåjÏtG‰›Où=Æϸ±J¡4Õ%­?ÒhÖg=eY'šHšc¸uÛ=èîcku)ÞT¤¡ßJ×s ÁÆlØ,EÓG_JR5m>-? NÊCø ô<ÿºßÏPÃDG‹
_쵄J Îˆv‘,Ô—WÊQÐåU{¹Q-ö¶å`‘ßàC›Î~1‰HFßåÌS¢ìxHî6°Ý·1£õ²ŒˆBB¨íì}Ãð`ÃX$Y]jç×̪0jDŠ:-D®q»}¢~$=5ÝTü)Qc<> ÷Ú+}úàÃ,6”Vp³tUE ϸyíä¡n<B䥾±-÷»";ò(WùxÛTÁHê²…WÜ—Q{Y§×e,i—_Ê’ýºWµ–k˜¨Š‹œ¦>vº‡uØÕ“Ø&¯RöW2Ç£
?»åk’Î//¿Ø° ³Ô滯_|ɯÅ$†§¸“#Ùåqÿ^éIG–“­œ' ‰e··qbÕ0<?!W¯¼¢]¨°Ò4ØSã´_ÊÅ †±™Ëi÷9—¨•l=¾ÛÊÆ<\tÛ;ò’Ä«›1ßãVw$xÿœyŽ¶‘n£N|yŸYâ®ÓGuÿG~É»\­ÑP,îßiÒáõ>Ç<ÁzäñJȾü¤K.^WÍ×ÖÞȇóyùÖ] S Ýb¼=ñTb7üŸê±Ÿ¾î+­àl¼öíµ~5®ÖP¤
m¬BaD3µ ê,e6rêQwæ-X±HìGÅä—àŠ0Ö6‰°Eá_þÉyÄ‘)ðÏ>Q~o‘Ú.È;ïŸê¬

But I didn't like this either. It's not human readable, but it still identifies itself as a memory stream and it could be easily deserialized by some lowlife hacker. It's not encrypted.

So I finally found a neat command called ProtectedMemory.Protect(). So I changed my method again and called serialize again on my MemoryStream and setting the MemoryProtectionScope to SameLogon. This makes the data encrypted and can ONLY be decrypted by the same Windows Logon that created the file. So now my file looked like this in Notepad:

ÿÿÿÿ oÕž¼P„«­'·ÚÍöVcRßámv9Á?f»iƒ¸—Zsz»*ŠØV”?ÌlõÜCæÖ»E,ó’6Àâc®TƒÅút>?Õâ[oË'¨*˜øóÃ[k}LjAöy¯ËØ[g:î3€­´$SõÅäYçÖŠ`ØæðF¶Ç„¸
ã³ÖᢠÌ;‡[’ž‰aÈ£¨NÙ žrã‹œÆDdž“iKšä»¾¬¤cìب¸ß¯ÄtØy¦1[ ûCÕû&…¦ITƦQž Ù1Øú”KJ!XÁUY{ô‚xÁrhRmäßþ§G†Ï©ˆcÛv]Md¬ˆ|„-@ï¹ ª})6ŸÌ(¡xéôÍñÉEÃg+C¬‡#ÒÀ%¾–ùȦ©è1ƒ6¾uå]ò¡Z!ùk61ÿ Ür“™ÎÜWÓl%o2æ€h» oa“›#GÎDŠMº7œ,甓‰æ›ñàÑÅDȱ·
Kì“ËfÕƵ·Ù£
<pI"™`†A¶ôàÑõWÙÞâ
SkûòOl½ïέ'ˆš³7E§žšëõÁ@.•ðÍÒÜbs|ò±ÕÄZÍá-|OtŸD-`xWÃm¥k:*ÔÃŽ(œáL€ôíùÑi§½3\Æ^ÚïЬ@–gˆíøSP™“Õ´·Ÿ
{º]DÒ{Œ4

Now not only is it encrypted, but if someone manages to decrypt it, they are presented with a memory stream to deserialize as well! It's not perfect, but it can throw some would be data thiefs for a loop, at least for a while.

Here's the class, I hope someone can use it instead of messing around for hours :)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Cryptography;
using System.Windows.Forms;

namespace DATA
{
    /// <summary>
    /// User Settings file. This holds all of the settings for the local installation of the application.
    /// This includes:
    /// 
    /// UI variables - Variables that set or get various UI features
    /// System variables - Used for statistical reasons
    /// DBMS variables - Variables that the database management system module uses
    /// </summary>
    [Serializable]
    class LocalSettings
    {
        //UI variables
        public bool BalloonHelp;
        
        //System variables
        public int SysRunNumber;
        public bool SysFirstRun;

        //DBMS variables
        public string dbmsConnectionType;
        public string dbmsDataSource;
        public string dbmsUserName;
        public string dbmsPassword;
        public string dbmsCatalog;
        public bool dbmsIntegratedSecurity;
        public bool dbmsPersistSecurity;

        /// <summary>
        /// Saves an instance of user settings to the user settings file
        /// </summary>
        /// <param name="settingsToSave">The instance to save</param>
        public void Save(LocalSettings settingsToSave)
        {
            try
            {
                FileStream FStream = new FileStream(
                    Path.GetDirectoryName(Application.ExecutablePath) + "User.usf",
                    FileMode.OpenOrCreate);
                BinaryFormatter BinaryDataFormatter = new BinaryFormatter();
                MemoryStream MemStream = new MemoryStream();
                BinaryDataFormatter.Serialize(MemStream, settingsToSave);
                byte[] toEncrypt = MemStream.GetBuffer();
                ProtectedMemory.Protect(toEncrypt, MemoryProtectionScope.SameLogon);
                BinaryDataFormatter.Serialize(FStream, toEncrypt);
                FStream.Close();
            }
            catch (Exception Exc)
            {
                MessageBox.Show("Could not save your settings.\n\n" + 
                    Exc.Message, 
                    Exc.Source, 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Loads the settings last saved from the settings file
        /// </summary>
        /// <returns>Returns an instance of the LocalSettings class</returns>
        public LocalSettings Load()
        {
            try
            {
                FileStream FStream = new FileStream(
                    Path.GetDirectoryName(Application.ExecutablePath) + "User.usf",
                    FileMode.Open);
                BinaryFormatter BinaryDataFormatter = new BinaryFormatter();
                MemoryStream MemStream = new MemoryStream();
                byte[] protectedMemory = (byte[])BinaryDataFormatter.Deserialize(FStream);
                ProtectedMemory.Unprotect(protectedMemory, MemoryProtectionScope.SameLogon);
                MemStream.Write(protectedMemory, 0, protectedMemory.Length);
                MemStream.Position = 0;
                LocalSettings LoadedSettings = (LocalSettings)BinaryDataFormatter.Deserialize(MemStream);
                FStream.Close();
                return LoadedSettings;
            }
            catch (Exception Exc)
            {
                MessageBox.Show("Could not load your settings.\n\n" +
                    Exc.Message,
                    Exc.Source,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                return null;
            }
        }
    }
}

Cheers!

This is an excellent way to protect a temporary file of data that might be passed between processes while the user is logged on.
However, the ProtectedMemory encryption is only valid during the current user session and therefore the saved data is not retrievable if the user logs off and back on.
Better to use one of the bidirectional cryptography classes to do the encryption and decryption. (RC2, DSA and RSA)

One simple way to get a none human readable file is to use the System.IO.Compression.GZipStream class to zip/unzip the data. This has the advantage of both compressing the data and making it none human readable in one go and is 'decrypt-able' from any user session.
Probably easy to crack if you know the signature of a zipped data stream, but to the casual hacker the data is unreadable.

Edited 6 Years Ago by nick.crane: n/a

Hmmm...Excellent point. I was unaware of the current user session requirement. I'll have to do some tweeking to it and see if I can encrypt with a different method.

Just seemed like all of the methods out there required you serialize the data to disk first, then re-serialize the new file with an encryptor...that's unacceptable to me. Recovering deleted, moved or otherwise "ghost" files is not hard. Granted, the data remains in memeory as well until the GC comes by, but still, that would take a bit more of an advanced hacker to grab it. It works well if you want to prevent some hacker from copying the file and trying to crack it on a different computer. The local machine I'm not worried about...if you lack the security on your work station, that's the user's fault and I can't prevent that.

Thanks for the info though! That could have left me frustrated again!

This article has been dead for over six months. Start a new discussion instead.