I'm developing a game using Unity engine. Currently, I'm on my very first journey to creating a secure and universal game data saving/loading. My game is able to succesfully save its data (game progress) and metadata (custom savable types, encapsulating the data, and necessary for succesfull data deserialization) into two files, but when it comes to loading the data, a weird error occurs upon decoding. It appears a really weird one to me because I googled similar error topics but wasn't able to find a satisfying answer.
The error and its stacktrace are:
CryptographicException: Bad PKCS7 padding. Invalid length 0.
Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException (System.Security.Cryptography.PaddingMode padding, System.Int32 length, System.Int32 position) (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt (System.Byte[] inputBuffer, System.Int32 inputOffset, System.Int32 inputCount) (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock (System.Byte[] inputBuffer, System.Int32 inputOffset, System.Int32 inputCount) (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
System.Security.Cryptography.CryptoStream.FlushFinalBlock () (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
System.Security.Cryptography.CryptoStream.Dispose (System.Boolean disposing) (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
System.IO.Stream.Close () (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
System.IO.StreamReader.Dispose (System.Boolean disposing) (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
System.IO.TextReader.Dispose () (at <9aad1b3a47484d63ba2b3985692d80e9>:0)
AuxMath.Decode (System.String input, System.Security.Cryptography.Aes decoder, System.Text.Encoding encoding) (at Assets/Scripts/Misc/AuxMath.cs:191)
SavingSystem.TryLoadMetadata (System.Security.Cryptography.Aes decoder, System.Text.Encoding encoding) (at Assets/Scripts/Saving System/SavingSystem.cs:164)
Rethrow as Exception: Metadata loading failed!
SavingSystem.TryLoadMetadata (System.Security.Cryptography.Aes decoder, System.Text.Encoding encoding) (at Assets/Scripts/Saving System/SavingSystem.cs:180)
SavingSystem.Load () (at Assets/Scripts/Saving System/SavingSystem.cs:82)
SavingSystem.Awake () (at Assets/Scripts/Saving System/SavingSystem.cs:43)
My saving/loading.
private void Save()
{
Aes encoder = Aes.Create();
encoder.Key = _keyContainer.Key;
PrepareSavableData();
SaveGameData(encoder, Encoding.UTF8);
SaveMetadata(encoder, Encoding.UTF8);
SavegameCompleted?.Invoke(this, EventArgs.Empty);
}
private bool Load()
{
Aes decoder = Aes.Create();
decoder.Key = _keyContainer.Key;
if (TryLoadMetadata(decoder, Encoding.UTF8) && TryLoadGameData(decoder, Encoding.UTF8))
{
return true;
}
return false;
}
The key for encryption is created randomly using default Aes settings and stored inside a KeyContainer ScriptableObject.
Here is the actual saving.
private void PrepareSavableData()
{
foreach (var entity in _registeredEntities)
{
_storedStates[entity.ID] = entity.GetState();
}
}
private void SaveMetadata(Aes encoder, Encoding encoding)
{
using FileStream fileStream = new(MetadataPath, FileMode.Create, FileAccess.Write);
using StreamWriter writer = new(fileStream, encoding);
List<string> knownTypesNames = new(_knownSavableDataCustomTypes.Count);
foreach (var type in _knownSavableDataCustomTypes)
{
knownTypesNames.Add(type.ToString());
}
string data = AuxMath.SerializeObjectToString(knownTypesNames, encoding);
string encodedData = AuxMath.Encode(data, encoder, encoding);
writer.Write(encodedData);
writer.Close();
}
private bool TryLoadMetadata(Aes decoder, Encoding encoding)
{
if (File.Exists(MetadataPath))
{
try
{
using FileStream fileStream = new(MetadataPath, FileMode.Open, FileAccess.Read);
using StreamReader reader = new(fileStream, encoding);
string encodedData = reader.ReadToEnd();
string decodedData = AuxMath.Decode(encodedData, decoder, encoding);
var knownTypesNames = AuxMath.DeserializeStringToObject<List<string>>(decodedData, encoding, _knownSavableDataCustomTypes);
HashSet<Type> knownTypes = new(knownTypesNames.Count);
foreach (var typeName in knownTypesNames)
{
knownTypes.Add(Type.GetType(typeName));
}
_knownSavableDataCustomTypes.UnionWith(knownTypes);
return true;
}
catch (Exception e)
{
throw new Exception("Metadata loading failed!", e);
}
}
return false;
}
private void SaveGameData(Aes encoder, Encoding encoding)
{
using FileStream fileStream = new(SavegamePath, FileMode.Create, FileAccess.Write);
using StreamWriter writer = new(fileStream, encoding);
string data = AuxMath.SerializeObjectToString(_storedStates, encoding);
string encodedData = AuxMath.Encode(data, encoder, encoding);
writer.Write(encodedData);
writer.Close();
}
private bool TryLoadGameData(Aes decoder, Encoding encoding)
{
if (File.Exists(SavegamePath))
{
try
{
using FileStream fileStream = new(SavegamePath, FileMode.Open, FileAccess.Read);
using StreamReader reader = new(fileStream, encoding);
string encodedData = reader.ReadToEnd();
string decodedData = AuxMath.Decode(encodedData, decoder, encoding);
_storedStates = AuxMath.DeserializeStringToObject<Dictionary<string, IEnumerable<object>>>(decodedData, encoding, _knownSavableDataCustomTypes);
return true;
}
catch (Exception e)
{
throw new Exception("Game data loading failed!", e);
}
}
return false;
}
I'm using DataContractSerializer to convert custom object types with a valuable game data to XML string representation in preparation for encoding/decoding.
public static string SerializeObjectToString(object obj, Encoding encoding)
{
if (obj is null)
{
throw new ArgumentNullException($"{nameof(obj)}", "Cannot serialize a null object!");
}
using MemoryStream memoryStream = new();
using StreamReader reader = new(memoryStream, encoding);
DataContractSerializer serializer = new(obj.GetType());
serializer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
return reader.ReadToEnd();
}
public static T DeserializeStringToObject<T>(string objectAsXml, Encoding encoding, IEnumerable<Type> knownTypes)
{
if (string.IsNullOrEmpty(objectAsXml))
{
throw new ArgumentNullException($"{nameof(objectAsXml)}", "Data is empty!");
}
if (knownTypes is null)
{
throw new ArgumentException("Known types are not supplied! Deserialization will fail!", $"{nameof(knownTypes)}");
}
using MemoryStream memoryStream = new();
byte[] xmlAsBytes = encoding.GetBytes(objectAsXml);
DataContractSerializer deserializer = new(typeof(T), knownTypes);
memoryStream.Write(xmlAsBytes, 0, xmlAsBytes.Length);
memoryStream.Position = 0;
if (deserializer.ReadObject(memoryStream) is T value)
{
return value;
}
else
{
throw new Exception("Passed data is invalid or corrupted and cannot be restored!");
}
}
Finally, encoding and decoding. Encryption algorithm gets a new initialization vector on every encoding. It gets written unencryptedly directly into the stream, before the encrypted stream writes the secured data. Upon decryption it is necessary to read 16 bytes first from the stream, as they represent the decryption initialization vector.
public static string Encode(string input, Aes encoder, Encoding encoding)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException($"{nameof(input)}", "Attempted to encode an empty input!");
}
if (encoder is null)
{
throw new ArgumentNullException($"{nameof(encoder)}", "Encoder is not set!");
}
encoder.GenerateIV();
using MemoryStream memoryStream = new();
using CryptoStream encodingStream = new(memoryStream, encoder.CreateEncryptor(), CryptoStreamMode.Write);
using StreamWriter encodedWriter = new(encodingStream, encoding);
memoryStream.Write(encoder.IV);
encodedWriter.Write(input);
memoryStream.Position = 0;
encodedWriter.Close();
return encoding.GetString(memoryStream.ToArray());
}
public static string Decode(string input, Aes decoder, Encoding encoding)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException($"{nameof(input)}", "Attempted to decode an empty input!");
}
if (decoder is null)
{
throw new ArgumentNullException($"{nameof(decoder)}", "Decoder is not set!");
}
using MemoryStream memoryStream = new();
memoryStream.Write(encoding.GetBytes(input));
byte[] iv = new byte[decoder.IV.Length];
memoryStream.Read(iv, 0, decoder.IV.Length);
decoder.IV = iv;
using CryptoStream decodingStream = new(memoryStream, decoder.CreateDecryptor(), CryptoStreamMode.Read);
using StreamReader decodedReader = new(decodingStream, encoding);
return decodedReader.ReadToEnd();
}
The following code both works perfectly and is a lot shorter:
public static byte[] Encrypt(byte[] input, KeyContainer container)
{
byte[] iv = container.GetNewIV();
using SymmetricAlgorithm algorithm = Aes.Create();
using ICryptoTransform encryptor = algorithm.CreateEncryptor(container.GetKey(), iv);
List<byte> output = new(iv.Length + input.Length + algorithm.BlockSize / 8);
byte[] encryptedInput = encryptor.TransformFinalBlock(input, 0, input.Length);
output.AddRange(iv);
output.AddRange(encryptedInput);
algorithm.Clear();
return output.ToArray();
}
public static byte[] Decrypt(byte[] input, KeyContainer container)
{
byte[] iv = input[..KeyContainer.IVLength];
byte[] encryptedInput = input[KeyContainer.IVLength..];
using SymmetricAlgorithm algorithm = Aes.Create();
using ICryptoTransform decryptor = algorithm.CreateDecryptor(container.GetKey(), iv);
byte[] output = decryptor.TransformFinalBlock(encryptedInput, 0, encryptedInput.Length);
algorithm.Clear();
return output;
}
Instead of referring to an Aes instance directly a more generalized SymmetricAlgorithm must be used because AES is one of the implementations of a symmetric encryption.
Instead of trying to perform cryptographic transformations implicitly by using ICryptoTransform as a backing resource inside a stream, one must be performed by calling TransformFinalBlock() method.
After all the crypto activity is done, any memory containing valuable data must be overridden by calling the Clear() method on a symmetric algorithm instance. This way hackers cannot recower any data to attempt and actually succesfully decrypt the result.
Here I have a class I've created for the purpose of sending text-files and images over a serial port:
public class SendItem
{
private byte[] bytes;
private string _fileName;
private string _extension;
private int bytesSin;
public string Extension
{
get { return _extension; }
set { _extension = value; }
}
public string FileName
{
get { return _fileName; }
set { _fileName = value; }
}
public byte[] Bytes
{
get { return bytes; }
set { bytes = value; }
}
public SendItem()
{
}
public void SendFile(SerialPort serialPort1)
{
if (serialPort1.IsOpen)
{
OpenFileDialog OFDialog = new OpenFileDialog();
OFDialog.Title = "Open File";
OFDialog.Filter = "Text Files (*.txt)" + "|*.txt|All files (*.*)|*.*";
OFDialog.InitialDirectory = #"C:\";
bool? userClickedOK = OFDialog.ShowDialog();
if (userClickedOK == true)
{
serialPort1.DiscardInBuffer();
string chosenFile = OFDialog.FileName;
string ext = Path.GetExtension(OFDialog.FileName);
_extension = ext;
byte[] data = File.ReadAllBytes(chosenFile);
SendItem newSendItem = new SendItem();
newSendItem._extension = ext;
newSendItem._fileName = System.IO.Path.GetFileNameWithoutExtension(chosenFile);
newSendItem.bytes = data;
byte[] view = newSendItem.Serialize();
string test = Convert.ToBase64String(data);
serialPort1.Write(newSendItem.Serialize(), 0, newSendItem.Serialize().Length);
//serialPort1.Write(data, 0, data.Length);
}
}
}
public override string ToString()
{
return string.Format("File: {0}{1}",_fileName, _extension);
}
}
There is quite a few redundant things in the SendFile method, but I ask that it would be ignored in favour of the following issue I keep on having.
Whenever I send a text file or just plain chat text from a textbox, the following block of code is executed without triggering the catch:
void _sPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
byte[] data = new byte[sp.BytesToRead];
string message = string.Empty;
sp.Read(data, 0, data.Length);
try
{
SendItem receivedObject = data.Deserialize<SendItem>();
File.WriteAllBytes(#"d:\" + receivedObject.FileName + receivedObject.Extension, receivedObject.Bytes);
message = "File has been recieved.";
sp.Write("File sent.");
}
catch (Exception exp)
{
errors = exp.Message;
message = Encoding.UTF8.GetString(data);
}
App.Current.Dispatcher.Invoke(new Action(() => _response.Add("Friend: " + message)));
}
The problem comes in when I try to send an image... It trigger the catch and gives the exception There is an error in XML document (5, 3870).
My serializer and deserializer I've written is as follow:
public static byte[] Serialize<T>(this T source)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
MemoryStream stream = new MemoryStream();
serializer.Serialize(stream, source);
byte[] buffer = stream.GetBuffer();
stream.Close();
return buffer;
}
public static T Deserialize<T>(this byte[] source)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
MemoryStream stream = new MemoryStream(source);
T result = (T)serializer.Deserialize(stream);
stream.Close();
return result;
}
Can anyone point out where I'm making my mistake? I've been debugging it for ages and I can't wrap my head around it.
**EDIT:
I am also including the serialised data for an image I tried to send, after looking at it, it appears my Byte might be too big for the serial port - is there any way to adjust this or allow the serial port to take all of the data sent over?
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
It may or may not be the whole problem, but this is definitely a problem:
byte[] buffer = stream.GetBuffer();
That's quite possibly returning more data than you want - because it's not limiting itself to the length of the stream. MemoryStream has the handy MemoryStream.ToArray method to simplify this:
public static byte[] Serialize<T>(this T source)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
MemoryStream stream = new MemoryStream();
serializer.Serialize(stream, source);
return stream.ToArray();
}
(There's no benefit from closing a MemoryStream that you've created like this - and if you did feel you needed to, you should use a using statement.)
Likewise your Deserialize method can be simplified to:
public static T Deserialize<T>(this byte[] source)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
return (T) serializer.Deserialize(new MemoryStream(source));
}
Given that you were effectively trying to deserialize "the XML document - and then probably a load of "blank" data, that may well be the issue.
EDIT: Now there's a second problem:
byte[] data = new byte[sp.BytesToRead];
string message = string.Empty;
sp.Read(data, 0, data.Length);
You're assuming that all of the data is ready to read in a single go. That won't be true if there's more data than the buffer size of the serial port. Instead, if you're trying to put multiple "messages" onto what is effectively a stream, you'll need to either indicate the end of a message (and keep reading until you find that) or write the length of the message before the message itself - then when reading, read that length and then make sure you read that many bytes.
This is a standard issue when you try to use a stream-oriented protocol as a message-oriented protocol.
You should use ToByteArray() instead of GetBuffer().
This is a method, for encrypting a text and returning the cipher text:
public static string Encrypt(this string clearText, CryptologyMethod method)
{
ICryptoTransform cryptoTransform = null;
switch (method)
{
case CryptologyMethod.TripleDes:
cryptoTransform = new TripleDESCryptoServiceProvider().CreateEncryptor(Config.TripleDesKey, Config.TripleDesIV);
break;
case CryptologyMethod.AES:
cryptoTransform = new AesCryptoServiceProvider().CreateEncryptor(Config.AesKey, Config.AesIV);
break;
}
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(clearText);
cryptoStream.FlushFinalBlock();
return memoryStream.Text();
}
}
}
}
public enum CryptologyMethod
{
TripleDes,
AES
}
Text() is an extension method, to read the content of a stream:
public static string Text(this Stream stream)
{
return new StreamReader(stream).ReadToEnd();
}
Also, Config.TripleDesKey and other properties return array of bytes read from configuration file.
The problem is that when I use this extension method, the result is always an empty string:
string cipherText = "some clear text".Encrypt(CryptologyMethod.TripleDes);
// cipherText is empty
I think I've had that issue before, the solution was pretty obvious once I figured it out and completely opaque before then.
The problem was that ReadToEnd is relative, as in "from current location to end". The problem being that "current position" already WAS "end" because I'd just finished writing to the end of the stream. So I would fix your problem like this:
public static string Text(this Stream stream)
{
stream.Position = 0
return new StreamReader(stream).ReadToEnd();
}
Worked for me with a similar issue, hope it helps.
I am trying to convert class to byte but not worked.
I receive a byte[] (b) with zero length . why ?
void start()
{
Item item = new Item();
item.files.Add(#"test");
byte[] b = ObjectToByteArray(item); // b will have zero length
}
[Serializable]
public class Item : ISerializable
{
public Item()
{
files = new List<string>();
Exclude = false;
CleanEmptyFolder = false;
}
public List<string> files;
public string MusicProfileName;
public bool Exclude;
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("files", files);
info.AddValue("MusicProfileName", MusicProfileName);
info.AddValue("Exclude", Exclude);
}
#endregion
}
public byte[] ObjectToByteArray(object _Object)
{
using (var stream = new MemoryStream())
{
// serialize object
var formatter = new BinaryFormatter();
formatter.Serialize(stream, _Object);
// get a byte array
var bytes = new byte[stream.Length];
using (BinaryReader br = new BinaryReader(stream))
{
bytes = br.ReadBytes(Convert.ToInt32(stream.Length));
}
return bytes;
}
}
You didn't reset the MemoryStream before you started to read from it. The position is at the end of the stream, so you get an empty array.
Use the ToArray method instead, it gets the entire content regardless of the current position:
public byte[] ObjectToByteArray(object _Object) {
using (var stream = new MemoryStream()) {
// serialize object
var formatter = new BinaryFormatter();
formatter.Serialize(stream, _Object);
// get a byte array
return stream.ToArray();
}
}
Try using MemoryStream.ToArray instead of trying to read from the MemoryStream with a BinaryReader:
return stream.ToArray();
The problem is that you aren't resetting the stream, so it's trying to read from the end instead of the beginning. You could also seek back to the beginning of the stream:
stream.Seek(0, SeekOrigin.Begin);
There are 2 buttons which will generate 2 piece of data. What I need to achieve is encrypt them separately and export to same file. Then I need be able to decrypt it later.
Is it possible to use CBC mode? Using the same stream to encrypt the 1st block of the 2nd piece of data by the last block of the 1st piece of data? (To avoid IV)
Or
I can do this in ECB mode? Would be good if someone can elaborate on this.
You could always store the two values as separate properties of the file.
[Serializable]
public class EncryptedValues
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public static EncryptedValues FromXml(string xmlString)
{
if (!string.IsNullOrEmpty(xmlString))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(EncryptedValues));
using (MemoryStream memoryStream = new MemoryStream())
{
using (StreamWriter streamWriter = new StreamWriter(memoryStream))
{
streamWriter.Write(xmlString);
streamWriter.Flush();
memoryStream.Flush();
memoryStream.Position = 0;
return (xmlSerializer.Deserialize(memoryStream) as EncryptedValues);
}
}
}
return null;
}
public string ToXml()
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(EncryptedValues));
using (MemoryStream memoryStream = new MemoryStream())
{
using (StreamWriter streamWriter = new StreamWriter(memoryStream))
{
xmlSerializer.Serialize(streamWriter, o);
streamWriter.Flush();
memoryStream.Flush();
memoryStream.Position = 0;
using (StreamReader streamReader = new StreamReader(memoryStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}