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!