Nick

My concern is just the binary file. I am seeking way to protect the animations. Its fine they could see the attachment pieces from the texture file.

Is there any place I could set a handler or override something so that I can make the runtime to load from a decrypted copy in memory?
Nick
  • Postovi: 300

Harald

While there are no built in hooks available yet, you could extend the SkeletonDataAsset functionality by either creating a SkeletonDataAsset subclass and overwriting GetSkeletonData().

The relevant import line that is loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes..) here, before which you would add your pre-processing step.

Another way would be to create a new class like SkeletonRawDataModifierAsset that behaves similar to SkeletonDataModifierAsset. You would add a list of SkeletonRawDataModifierAsset (similar to this line) and call the processing callback in ReadSkeletonData.

Please let us know if this is suitable for your scenario, also whether you experience any issues with it along the way. We would add such a SkeletonRawDataModifierAsset feature officially if it is beneficial for other users as well, therefore it is important to know if that would help in your case, or if there are problems along the way which we didn't consider.
Avatar
Harald

Harri
  • Postovi: 4101

Nick

I did a little test and found some issues.
Will update you later.

---

Here is the update.

First of all, I am just coding with the 4.0.x spine unity runtime, things maybe different for latest version.

Because I don't want to keep patching the runtime code to integrate encryption in the future. I tried to do the implementation as generic as possible. It should be no problem adding them to the runtime code base. The modification to the original runtime code base just just 3 lines excluding bug fixes. Two files are added.

EncryptionPanel.cs
- This is the UI to test and config encryption.

Unity_0IyR8Ka6ur.png

In practice, you can actually write more code to automatically encrypt every spine skel.bytes files during import when encryption setting is on in the project saving player more mouse clicks. I just leave the current progress this way as I don't really need it.

SpineEncryption.cs

The class SpineEncryption is used to decode bytes from encrypted file.

To modify the runtime to use this class, all we need to do is to insert a line of code before passing the bytes down any parsing methods. It will automatically do the stuff inside.
var bytes = SpineEncryption.GetBytes(skeletonJSON.bytes);
Below is the three locations that need to inject the code.
devenv_inY7lQd2gb.png



The GetBytes() method will try to detect if its encrypted or not using the existing SkeletonBinary.GetVersionString() so both encrypted and non-encrypted file will be parsed correctly.

While writing this part of the code, I found that the GetVersionStringOld3X() is written incorrectly, it doesn't throw Exception like the GetVersionString() do and could try to read thousand of bytes for the version string. Not sure if newer version of runtime has it fixed. Anyway, I changed it slightly to this to make them consistent so that encryption code can catch any exception correctly:
public string GetVersionStringOld3X () {
// Hash.
int byteCount = ReadInt(true);
if (byteCount > 1) input.Position += byteCount - 1;

// Version.
byteCount = ReadInt(true);
if (byteCount > 1 && byteCount < 32) // check max length!
{
byteCount--;
var buffer = new byte[byteCount];
ReadFully(buffer, 0, byteCount);
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
//return null; // throw as new implementation!
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n");
}
I suggest to further check if final string only contains valid chars. e.g. numbers, '.' , 'beta', 'alpha', etc
There is still a chance the byteCount value read from encrypted file is just slip through the length check.

Possible Improvements
The current implementation relies on EncryptionPanel to auto restore the latest setting. e.g. whether encryption is enabled or not and also the value of saved encryption Key and IV for the editor.

The code is:
private void OnEnable()
{
key = EditorPrefs.GetString("spine.encryption.key");
iv = EditorPrefs.GetString("spine.encryption.iv");

var enableEncryption = EditorPrefs.GetBool("spine.enableEncryption");

SetEncryptionEnabled(enableEncryption);
}


void SetEncryptionEnabled( bool enabeld)
{
if (enabeld)
{
SpineEncryption.UseDefaultEncryption(key, iv);
}
else
{
SpineEncryption.UseNoEncryption();
}

EditorPrefs.SetBool("spine.enableEncryption", enabeld);
}
If EncryptionPanel is closed and project code get recompiled due to code change, it does not restore the setting to enable encryption. This would lead to error parsing encrypted files. I am ok with the EncryptionPanel always being opened.
If you want it to work properly without the panel staying open, all you need to do is to run the same code like above somewhere when recompilation is detected.

Another improvement I realize after I finished the thing is that it is better to save the setting to a file instead of using EditorPrefs so that the setting can be pushed to project git and be shared among peoples. But I am a one man team so it doesn't bother me and I just leave it this way. 8)

Optionally, you can code it to scan for skel.bytes file in the project folder and check or auto-encrypt them in one go. That way user don't need to encrypt the files one by one by hand. Doing it during import may not be better because there could be exported files in the project already.

Optionally 2, in theory, the encryption core can also be used for texture files too. This is not my goal so I just leave it.

How to use?

After integrating the code to the runtime, in editor, we can open the EncryptionPanel from
Menu > Windows > Spine > Encryption Panel
Unity_0IyR8Ka6ur.png


There you see Generate button that can generate a new Key/IV pair for encryption / decryption.

Key consistency is to check whether the key you see in the panel is actually the key in use in the runtime.

To encrypt/decrypt a file, select any file containing ".skel.bytes" and press the Encrypt button.

You can also use the "Test" button to check if a file is encrypted or not. Note that it cannot decrypt old encrypted file if key/IV is changed. That means once you decided to use a key/IV pair, save a backup somewhere and don't lost it.

The panel is for editor only. In game, you need to actively call
SpineEncryption.UseDefaultEncryption( key, iv);
to tell the runtime to enable encryption.

You can also implement your own version of encryption using IEncryptionModule and assign it to
SpineEncryption.ActiveModule
About encryption

AES in unity mono is buggy so I used Rijndael instead. It should not be a problem as the goal here is just to prevent direct extraction of the animation files. It only add a thin layer to security. The current code is not mean to protect from professional hacking. Hacker can still decompile your game and find your key / iv and decrypt the file.

Enjoy


P.S.
I only tested it within Unity Editor. Use at your own risk.
Nemaš dopuštenje za pregledavanje privit(a)ka dodan(og)ih postu.
Nick
  • Postovi: 300

Harald

Thanks for sharing your insights and also your code, much appreciated!

We have officially added fixes regarding the 3.8 fallback version reading code to GetVersionStringOld3X. New 4.0 and 4.1-beta unitypackages are available for download:
Spine Unity Download
Nick je napisao/la:I suggest to further check if final string only contains valid chars. e.g. numbers, '.' , 'beta', 'alpha', etc
There is still a chance the byteCount value read from encrypted file is just slip through the length check.
We didn't add respective checks here, as it's parsed in these lines after the raw string is returned:
SkeletonDataCompatibility.cs: line 153
You might quite easily add the respective statements to your code in SpineEncryption.cs, line227 which currently discards the returned raw version string:
SkeletonBinary.GetVersionString(input);
Avatar
Harald

Harri
  • Postovi: 4101

Nick

You might quite easily add the respective statements to your code in SpineEncryption.cs, line227 which currently discards the returned raw version string:
I see. Glad its already fixed. Yes, it is one way to do it. It just feel weird that the function is called GetVersionString() and also has a throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); inside but it is able to return a broken string which seems against the naming and description of the function. :lol:
Nick
  • Postovi: 300

Harald

:nerd: Well, it reads GetVersionString (not Get_ValidVersion_String) and it does not return a "broken string", it returns a string :). Think of it as an "extract data" method. Consider a json key value pair like "version": "1.2.invalid", don't you expect it to return "1.2.invalid"? If the json file is broken and e.g. has no value assigned at "version", it might either return null or throw. Of course in the case of binary data it's always open to interpretation.

In general GetVersionString was not intended to be re-purposed as a general validity check out of context, and relying on exceptions being thrown for normal control flow is not something that is considered good coding style either ;).
Avatar
Harald

Harri
  • Postovi: 4101


Natrag na Unity