1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
|
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data;
using System.Security;
using System.Security.Cryptography;
using System.Xml;
namespace myNamespace
{
/// <summary>
/// Summary:
/// --------
/// The XMLEncryptor class provides methods to save and read a DataSet as an encrypted XML file.
///
/// Main functionality:
/// -------------------
/// 1. Save any DataSet to an encrypted XML file.
/// 2. Read the encrypted XML file back and present it to any program as a DataSet,
/// without creating temporary files in the process
/// 3. The encrypted XML files are marked with a signature that identifies them as being encrypted files.
///
/// Limitations:
/// ------------
/// 1. The class uses a user name and password to generate a combined key which is used for encryption.
/// No methods are provided to manage the secure storage of these data.
/// 2. Theoretically, the class can handle files up to 2GB in size. In practice, since conversions
/// are handled in memory to avoid having temporary (decrypted) files being written to the drive,
/// the practical size may be limited by available system resources.
/// </summary>
public class XMLEncryptor
{
private byte[] signature;
private string username, password;
const int BIN_SIZE = 4096;
private byte[] md5Key, md5IV;
private bool validParameters;
public XMLEncryptor(string username, string password)
{
this.username = username;
this.password = password;
if (username.Length + password.Length < 6)
{
validParameters = false;
// abort the constructor. Calls to public functions will not work.
return;
}
else
{
validParameters = true;
}
GenerateSignature();
GenerateKey();
GenerateIV();
}
#region Helper functions called from constructor only
/// <summary>
/// Generates a standard signature for the file. The signature may be longer than 16 bytes if deemed necessary.
/// The signature, which is NOT ENCRYPTED, serves two purposes.
/// 1. It allows to recognize the file as one that has been encrypted with the XMLEncryptor class.
/// 2. The first bytes of each XML file are quite similar (<?xml version="1.0" encoding="utf-8" ?>).
/// This can be exploite to "guess" the key the file has been encrypted with. Adding a signature of a reasonably
/// large number of bytes can be used to overcome this limitation.
/// </summary>
private void GenerateSignature()
{
signature = new byte[16] {
123, 078, 099, 166,
000, 043, 244, 008,
005, 089, 239, 255,
045, 188, 007, 033
};
}
/// <summary>
/// Generates an MD5 key for encryption/decryption. This method is only called during construction.
/// </summary>
private void GenerateKey()
{
MD5 md5 = new MD5CryptoServiceProvider();
StringBuilder hash = new StringBuilder(username + password);
// Manipulate the hash string - not strictly necessary.
for (int i = 1; i < hash.Length; i += 2)
{
char c = hash[i-1];
hash[i-1] = hash[i];
hash[i] = c;
}
// Convert the string into a byte array.
Encoding unicode = Encoding.Unicode;
byte[] unicodeBytes = unicode.GetBytes(hash.ToString());
// Compute the key from the byte array
md5Key = md5.ComputeHash(unicodeBytes);
}
/// <summary>
/// Generates an MD5 Initiakization Vector for encryption/decryption. This method is only called during construction.
/// </summary>
private void GenerateIV()
{
MD5 md5 = new MD5CryptoServiceProvider();
string hash = password + username;
// Convert the string into a byte array.
Encoding unicode = Encoding.Unicode;
byte[] unicodeBytes = unicode.GetBytes(hash);
// Compute the IV from the byte array
md5IV = md5.ComputeHash(unicodeBytes);
}
#endregion
#region Methods to write and verify the signature
private void WriteSignature(FileStream fOut)
{
fOut.Position = 0;
fOut.Write(signature, 0, 16);
}
private bool VerifySignature(FileStream fIn)
{
byte[] bin = new byte[16];
fIn.Read(bin, 0, 16);
for (int i = 0; i < 16; i++)
{
if (bin[i] != signature[i])
{
return false;
}
}
// Reset file pointer.
fIn.Position = 0;
return true;
}
#endregion
#region Public Functions
/// <summary>
/// Reads an encrypted XML file into a DataSet.
/// </summary>
/// <param name="fileName">The path to the XML file.</param>
/// <returns>The DataSet, or null if an error occurs.</returns>
public DataSet ReadEncryptedXML(string fileName)
{
FileInfo fi = new FileInfo(fileName);
FileStream inFile;
#region Check for possible errors (includes verification of the signature).
/*if (! validParameters)
{
Trace.Assert(false, "Invalid parameters - cannot perform requested action");
return null;
}
if (! fi.Exists)
{
Trace.Assert(false, "Cannot perform decryption: File " + fileName + " does not exist.");
return null;
}
if (fi.Length > Int32.MaxValue)
{
Trace.Assert(false, "This decryption method can only handle files up to 2GB in size.");
return null;
}*/
try
{
inFile = new FileStream(fileName/*fi.Name*/, FileMode.Open);
}
catch (Exception exc)
{
Trace.Assert(false, exc.Message + "Cannot perform decryption");
return null;
}
if (! VerifySignature(inFile))
{
Trace.Assert(false,"Invalid signature - file was not encrypted using this program");
return null;
}
#endregion
RijndaelManaged rijn = new RijndaelManaged();
rijn.Padding = PaddingMode.Zeros;
ICryptoTransform decryptor = rijn.CreateDecryptor(md5Key, md5IV);
// Allocate byte array buffer to read only the xml part of the file (ie everything following the signature).
byte[] encryptedXmlData = new byte[(int) fi.Length - signature.Length];
inFile.Position = signature.Length;
inFile.Read(encryptedXmlData, 0, encryptedXmlData.Length);
// Convert the byte array to a MemoryStream object so that it can be passed on to the CryptoStream
MemoryStream encryptedXmlStream = new MemoryStream(encryptedXmlData);
// Create a CryptoStream, bound to the MemoryStream containing the encrypted xml data
CryptoStream csDecrypt = new CryptoStream(encryptedXmlStream, decryptor, CryptoStreamMode.Read);
// Read in the DataSet from the CryptoStream
DataSet data = new DataSet();
try
{
data.ReadXml(csDecrypt, XmlReadMode.Auto);
}
catch (Exception exc)
{
Trace.Assert(false,exc.Message, "Error decrypting XML");
return null;
}
// flush & close files.
encryptedXmlStream.Flush();
encryptedXmlStream.Close();
inFile.Close();
return data;
}
/// <summary>
/// Writes a DataSet to an encrypted XML file.
/// </summary>
/// <param name="dataset">The DataSet to encrypt.</param>
/// <param name="encFileName">The name of the encrypted file. Existing files will be overwritten.</param>
public void WriteEncryptedXML(DataSet dataset, string encFileName)
{
FileStream fOut;
#region Check for possible errors
if (! validParameters)
{
Trace.Assert(false, "Invalid parameters - cannot perform requested action");
return;
}
#endregion
// Create a MemoryStream and write the DataSet to it.
MemoryStream xmlStream = new MemoryStream();
dataset.WriteXml(xmlStream, XmlWriteMode.WriteSchema);
// Reset the pointer of the MemoryStream (which is at the EOF after the WriteXML function).
xmlStream.Position = 0;
// Create a write FileStream and write the signature to it (unencrypted).
fOut = new FileStream(encFileName, FileMode.Create);
WriteSignature(fOut);
#region Encryption objects
RijndaelManaged rijn = new RijndaelManaged();
rijn.Padding = PaddingMode.Zeros;
ICryptoTransform encryptor = rijn.CreateEncryptor(md5Key, md5IV);
CryptoStream csEncrypt = new CryptoStream(fOut, encryptor, CryptoStreamMode.Write);
#endregion
//Create variables to help with read and write.
byte[] bin = new byte[BIN_SIZE]; // Intermediate storage for the encryption.
int rdlen = 0; // The total number of bytes written.
int totlen = (int) xmlStream.Length; // The total length of the input stream.
int len; // The number of bytes to be written at a time.
//Read from the input file, then encrypt and write to the output file.
while(rdlen < totlen)
{
len = xmlStream.Read(bin, 0, bin.Length);
if (len == 0 && rdlen == 0)
{
Trace.Assert(false,"No read");
break;
}
csEncrypt.Write(bin, 0, len);
rdlen += len;
}
csEncrypt.FlushFinalBlock();
csEncrypt.Close();
fOut.Close();
xmlStream.Close();
}
#endregion
}
} |
Partager