Fadi Banna Fadi Banna - 10 days ago 4
C# Question

Serializing an object to file with encryption in a UWP app

Ok so I have this method for serializing my player object to an XML file,
I've been trying to find a way to encrypt the data before writing the object to the file.
there are many posts here and over the internet concerning this topic and I tried to combine all the data so it works on my app and ended up with this:

public static async Task SaveAsync<T>(Player player)
{
IRandomAccessStream sessionRandomAccess = null;
string strAlgName = SymmetricAlgorithmNames.AesCbc;
UInt32 keyLength = 32;
CryptographicKey key;
SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);
IBuffer keyMaterial = CryptographicBuffer.GenerateRandom(keyLength);
key = objAlg.CreateSymmetricKey(keyMaterial);
IBuffer iv = null;
if (strAlgName.Contains("CBC"))
{
iv = CryptographicBuffer.GenerateRandom(objAlg.BlockLength);
}
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.ReplaceExisting);
sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite);
IBuffer buffEncrypt = CryptographicEngine.Encrypt(key, ReadFully(sessionRandomAccess.AsStream()).AsBuffer(), iv);
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player), new Type[] { typeof(T) });
Stream stream = buffEncrypt.AsStream();
sessionSerializer.WriteObject(stream, player);
await stream.FlushAsync();
}


And the method to convert the stream to bytes array:

public static byte[] ReadFully(Stream input)
{
using (MemoryStream ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}


Now it all works well until the WriteObject method called then I'll get this exception:


An exception of type 'System.NotSupportedException' occurred in
mscorlib.ni.dll but was not handled in user code

Additional information: Unable to expand length of this stream beyond
its capacity.


Anyone have any Idea how to solve this and why it actually occurs?
Thanks

Update:
I thought I might add the Decryption method that Worked for me as an addition to the great Answer that included the encryption part by Jay Zuo - MSFT.

so here it is:

public static async Task<Player> LoadAsync<T>()
{
try
{

StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.OpenIfExists);
if (sessionFile == null)
{
return null;
}
IBuffer buffEncrypt = await FileIO.ReadBufferAsync(sessionFile);
String strAlgName = SymmetricAlgorithmNames.AesCbcPkcs7;
CryptographicKey key; // Symmetric key
// Open a symmetric algorithm provider for the specified algorithm.
SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);
key = objAlg.CreateSymmetricKey(keyMaterial);
IBuffer buffMsg=CryptographicEngine.Decrypt(key, buffEncrypt, iv);
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player));
return (Player)sessionSerializer.ReadObject(buffMsg.AsStream());
}
catch (Exception e)
{
}
return null;
}


Note two things the iv IBuffer and the keyMaterial where stored and loaded from the database as Jay Zuo - MSFT suggested.
Hope this helps some one:)

Answer

To encrypt a serialized object, we can get the IBuffer that represents the serialized object first.

IBuffer buffMsg;

DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Player));
using (var stream = new MemoryStream())
{
    sessionSerializer.WriteObject(stream, player);

    buffMsg = stream.ToArray().AsBuffer();
}

Then we can encrypt the buffer like following:

IBuffer iv;                        // Initialization vector
CryptographicKey key;              // Symmetric key
UInt32 keyLength = 32;             // Length of the key, in bytes
String strAlgName = SymmetricAlgorithmNames.AesCbc;

// Initialize the initialization vector.
iv = null;

// Open a symmetric algorithm provider for the specified algorithm.
SymmetricKeyAlgorithmProvider objAlg = SymmetricKeyAlgorithmProvider.OpenAlgorithm(strAlgName);

// Determine whether the message length is a multiple of the block length.
// This is not necessary for PKCS #7 algorithms which automatically pad the
// message to an appropriate length.
if (!strAlgName.Contains("PKCS7"))
{
    if ((buffMsg.Length % objAlg.BlockLength) != 0)
    {
        throw new Exception("Message buffer length must be multiple of block length.");
    }
}

// Create a symmetric key.
IBuffer keyMaterial = CryptographicBuffer.GenerateRandom(keyLength);
key = objAlg.CreateSymmetricKey(keyMaterial);

// CBC algorithms require an initialization vector. Here, a random
// number is used for the vector.
if (strAlgName.Contains("CBC"))
{
    iv = CryptographicBuffer.GenerateRandom(objAlg.BlockLength);
}

// Encrypt the data and return.
IBuffer buffEncrypt = CryptographicEngine.Encrypt(key, buffMsg, iv);

Once we have the encrypted buffer, we can write it into a file:

StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("Data.xml", CreationCollisionOption.ReplaceExisting);

await FileIO.WriteBufferAsync(sessionFile, buffEncrypt);

Please note that SymmetricAlgorithmNames.AesCbc is a no padding algorithm. While using this algorithm, the message buffer length that we want to encrypt must be multiple of block length. As the buffer length of the serialized object is mutable, I'd suggest using a PKCS #7 algorithm such as SymmetricAlgorithmNames.AesCbcPkcs7 instead of SymmetricAlgorithmNames.AesCbc. And for decryption, you may also need to store Symmetric key and Initialization vector.

Comments