MODULE: EncryptedFile v0.9b

Started by HeirOfNorton, Mon 17/10/2005 05:28:56

Previous topic - Next topic

HeirOfNorton

Hey folks, I've got yet another script module for your consumption.  Are any of you making a game using the File I/O functions, perhaps for a player-generated character, or some other use? Were you perhaps worried that some unscrupulous player could hack the file and change the character's stats on a whim? Or perhaps you are using text files for some of the game's content, but don't want players to be able to change what's been written? Well, worry no more!  ;D

Ahem, anyway, this module adds an EncryptedFile class that is intended to be a drop-in replacement for the standard File objects already used. It uses a simple (but nearly impossible to break) Exclusive-Or encryption to secure your external files, so no one can read them but you. It also includes a small EXE for encrypting plain text/binary files so they can be read by the program (source code included).

Download here (Requires AGS v2.71!) (Thanks Neole!)
Mirror 1 (Thanks Colxfile!)
Mirror 2 (Thanks Candle!)

Please note that this is still a beta version (I'd love to hear about any bugs/issues/suggestions for the sucker), and that it requires AGS 2.71 or higher.

I hope you find it useful.
HoN

Ubel

#1
Interesting. Does this include video files too?

Kweepa

Quote from: HeirOfNorton on Mon 17/10/2005 05:28:56
It uses a simple (but nearly impossible to break) Exclusive-Or encryption to secure your external files, so no one can read them but you.

Heh. I remember Comanche Maximum Overkill's data files used XOR encryption with a 32 bit key (64 bit for the sequel). That was easy to crack, because the data was so regular.

Nice idea for a module!
Still waiting for Purity of the Surf II

SSH

#3
Indeed, people who know about cryptography never call anything "nearly impossible" to break. Every encryption can be broken by brute force attacks, it's just that good ones would require 1000 years or so of supercomputer time to do so, by which time you probably no longer care if it is broken. DES used to be resonabley secure, then triple DES, but now even AES is easily cracked.
12

HeirOfNorton

#4
Quote from: SSH on Mon 17/10/2005 10:29:15
Indeed, people who know about cryptography never call anything "nearly impossible" to break.

Yeah, I know, but I'm working on my salesman skills.   ;)
Obviously nothing this simple could be that hard to crack with a concerted effort, but it should be enough to deter any Joe Gameplayer with a hex editor who wants to give himself 10,000 experience points.

Quote from: Pablo on Mon 17/10/2005 05:37:37
Does this include video files too?

Well, the included encrypter WILL encrypt video files, but said encrypter would have to be used to decrypt them as well. They would not be usable in a game.

Ubel

I've been trying to use this module but there's this little problem that prevents me from doing so. It doesn't work. Whenever I type EncryptedFile. in the script editor, the autocomplete doesn't show me the functions from the struct that is defined in the module's script header, even though it should. And if I manually try typing the function it doesn't work either.

I believe there's something wrong in how the struct is defined but I just can't find the flaw even though I've searched through the whole script module. I guess my scripting skills are not quite enough for understanding the whole thing. Is anyone else encountering these same problems or is it just me for some weird reason? :-\

monkey0506

The flaw* that you seem to have noticed is that EncryptedFile isn't a static struct! Try something like this:

Code: ags
EncryptedFile ef;
ef.Open("file.enc", eFileWrite, "EncryptKey");
if (ef.GetFileHandle() == null) return;
ef.WriteString("this is some text!");
ef.Close();


*It's not actually a flaw. HON made it this way by design.

Ubel

Oh my, I hadn't realised that! Thank you very much monkey, and sorry for my noobness. :D

Dualnames

I'm probably be called a newbie but what the heck, well I checked your module. I read all the txt file. And managed to encrypt a video of me using the keyword "deadaswell" and the file from shoryuken.wmv was made to shoryuken.scbr(just an extension I came up with).
  Probably from what I've read I can't encrypt videos. Can I? Please write an example code. Thank you.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Rui 'Trovatore' Pires

QuoteDoes this include video files too?


Well, the included encrypter WILL encrypt video files, but said encrypter would have to be used to decrypt them as well. They would not be usable in a game.

So, you can't decrypt them in-game, so you can't encrypt videos for use in a game.
Reach for the moon. Even if you miss, you'll land among the stars.

Kneel. Now.

Never throw chicken at a Leprechaun.

monkey0506

#10
Would it be possible to do something like this (which would create a trash file, but might provide a solution)?

Code: ags
// encrypt the video prior to distribution...
EncryptedFile encVideo;
encVideo.Open("shoryuken.scbr", eFileWrite, "deadaswell");
File* video = File.Open("shoryuken.wmv", eFileRead);
if ((video == null) || (encVideo.GetFileHandle() == null)) {
  Display("Error encrypting video!");
  return;
  }
while (!video.EOF) {
  encVideo.WriteRawLine(video.ReadRawLineBack());
  }
encVideo.Close();
video.Close();
// delete the non-encrypted video file prior to distribution ;)

// decrypt the file for in-game use
EncryptedFile encVideo;
encVideo.Open("shoryuken.scbr", eFileRead, "deadaswell");
File* video = File.Open("shoryuken.wmv", eFileWrite);
if ((video == null) || (encVideo.GetFileHandle() == null)) {
  Display("Error decrypting video!");
  return;
  }
while (!encVideo.EOF()) {
  video.WriteRawLine(encVideo.ReadRawLine());
  }
encVideo.Close();
video.Close();
// play the video back
PlayVideo("shoryuken.wmv", 0, 0);
// but then destroy the video file afterwards (not the encrypted copy!)
File* video = File.Open("shoryuken.wmv", eFileWrite);
if (video == null) return;
video.WriteString("killme"); // clearly not valid WMV data ;)


Wouldn't something like that work???

Dualnames

Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

monkey0506

#12
Actually now I'm not sure that it would work....perhaps it's just me, but in my tests it seems that the encryption isn't working very well with videos. My encrypted video-file is loaded with instances of the key in plain text, and when I try to decrypt the video, these instances remain, invalidating the video data.

I'm looking into the cause...but...don't get your hopes up too high if your results are the same.

P.S. If anyone knows about the bitwise XOR operator, it appears to be the result of a character (A) that when XORed with a character taken from the key (B) results in B, provided that B is non-zero. I'm not entirely clear what circumstances this would occur in, but if it's of any significance, when opening the video in WordPad it seems the culprits are these strange box characters...which I can't copy and paste.

Dualnames

I know about the XOR operator.

A    B     Y
0    0     0
0    1     1
1    0     1
1    1     0

When A and B are different Y equals to 1.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

monkey0506

I know basically how XOR works. I'm just confused regarding the results that I'm seeing...which are apparently:

A ^ B = B where B != 0

So...every bit in A is 0...meaning that the resulting bits will all be the same as the bits in B? So then the reverse operation (i.e., (A ^ B) ^ B) wouldn't work?

Hmm...I just tested it and it seems that the reverse operation would work. I'll look into the module code to try and see if this is possibly a bug.

P.S. Decrypting the video in-game may possibly be a slow operation depending on the length of the video. Just something to keep in mind.

Monsieur OUXX

#15
Quote from: monkey_05_06 on Sat 07/07/2007 04:39:46
A ^ B = B where B != 0 would mean that every bit in A is 0

...or that A = B! Maybe you forgot this case?

I thought of OR instead of XOR... Sorry
 

Dualnames

Monkey I'd like to inform you that I tried to encrypt a video file and then decrypt it.
You said that there might be a slowdown while encrypting and decrypting, and it was. And a very big one. The file I made experiments with was a wmv size 45 kb and bit rate 105kb. So if this causes slowdowns imagine what a 1mb file would do. Anyway, if you want any help for betatesting this module trying to fix the slowdown in some way, feel free to PM me.
Thanks again.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Gilbert

I think the problem is with slow file access of the AGS file funtions.
To en/decrypt a file the module actually (I think) writes to a new file byte by byte, which would be VERY slow in general (as file functions are intended for small files like stats in QFG type games, etc. only), I think there isn't any way to "fix" those slowdowns, so it's never recommended to encrypt video files in an AGS game. Unless of cause, someone makes a plugin instead to handle the process, which would just kick Linux and Mac users away (I think it doesn't matter anyway, AFAIK the Linux port doesn't suppport video playback, not sure about the Mac port).

monkey0506

RTFM! ;D

Yes, video playback works on Mac. Regarding the decryption, it has to read in the file one byte at a time, decrypt the byte, write the byte to the new file, rinse and repeat. If it would be possible to run the decryption over a more extensive period of time, instead of just in a single loop it might be possible to decrypt the file while other game events are run. Not sure how you might want to handle that...but maybe it will give you an idea you could use.

As for me, I've been looking into this module, and I think I may have found an un-related issue with the method used for appending to files. I'm not 100% sure, but I think there might be a bug there. But for now I have to go to..."work". Which mostly involves me sitting on a bench out in the hot 90 - 100 F weather. When I get home I may have a chance to look at it, but I have to go to work early tomorrow. I'll be off Thursday, and don't have to work Friday until 2 PM (GMT -6:00)...so I'll try to keep you all posted on my findings.

Monsieur OUXX

Quote from: monkey_05_06 on Tue 10/07/2007 16:33:48
If it would be possible to run the decryption over a more extensive period of time, instead of just in a single loop it might be possible to decrypt the file while other game events are run. Not sure how you might want to handle that...

Good idea; It's usually how slow processings are synchronized with fast processings within a program.

It could be done this way : the programer could create a list of videos that are likely "about" to be viewed (for example, he knows that most likely the intro sequence will be played if the user clicks on "New game"). This list would be created as soon as possible (at game start) and updated as often as necessary.

Then in each game loop (repeatedly_execute_always) a given number of bytes from the next video in this list would be decrypted. When it gets entirely decrypted, the engine begins to decrypt the next video in our list.

In our example, that means that the few seconds after the game starts and before the player clicks on "New Game" (even if it's only 4 seconds) would be used to begin decryption.

 

monkey0506

I've opted to write my own module because it's easier to debug my own code than someone else's. One of the problems that I encountered was that when using the Append mode, HoN decided that the offset in the encryption key would be the same as the length of the file when read in one char at a time. In my tests, this isn't the case however.

Another problem would be with trying to decrypt a single char from an encrypted file. If the char wasn't written in as a char (i.e., if you wrote in an int) then the decryption would fail. Essentially this means that if you tried to do a step-by-step decryption one char at a time, it wouldn't work unless it had been written in using the same method.

To fix this problem, I'm converting all data to chars first, and then writing out the files one char at a time. This means that you can successfully decrypt the file one char at a time, no matter what was written into it. This method will also keep the index in the encryption key updated properly so that I can use my custom functions to get the proper offset when appending to a file.

This won't really solve any speed issues, in fact, it may be a bit slower (since I have to convert a 4 byte int into 4 one byte chars), but it may provide a more stable means for data encryption. Of course the credit goes to HoN for the original idea...but maybe HoN knew what he was doing when he labeled this as version 0.9. ;)

Monsieur OUXX

Quote from: monkey_05_06 on Tue 17/07/2007 21:24:45
I have to convert a 4 byte int into 4 one byte chars

I'm interested in the code you're using. I've written a little routine, but i get bugs with the heavier-weight char (byte), so my function bugs when converting an int   >256^3   or   <0 (the sign is stored in the last byte).

 

monkey0506

#22
I've written a custom class (though I wasn't sure what to name it, and I'm not excited about what I chose):

Code: ags
struct SplitInt {
  writeprotected char Bytes[4]; // store the "split" data
  import void Split(int i); // split I into 4 bytes; use this to set the Bytes property
  import int Merge(); // merge Bytes back into a single int and return it
  import static int MergeBytes(char left, char midleft, char midright, char right); // statically merges 4 bytes and returns the integer value
  };

void SplitInt::Split(int i) {
  int j = 0;
  while (j < 4) {
    this.Bytes[j] = (i >> (8 * j)) & 255; // shift relevant data for byte J into the right-most byte, then use &255 to grab only the right-most byte
    j++;
    }
  }

int SplitInt::Merge() {
  int i = 0;
  int j = 3;
  while (j >= 0) {
    i += (this.Bytes[j] << (8 * j));
    j--;
    }
  return i;
  }

static int SplitInt::MergeBytes(char left, char midleft, char midright, char right) {
  int i = (left << 24);
  i += (midleft << 16);
  i += (midright << 8);
  i += right;
  return i;
  }


It took me a bit of trial-and-error since this is technically the first time I've ever really used the bitwise operators, but it appears to be fully functional, even with numbers >256^3 and <0. ;)

Quote from: Monsieur OUXX on Tue 24/07/2007 16:24:02(the sign is stored in the last byte).

I assume you mean bit not byte...and by "last" I hope you mean left-most, because that is where the sign bit is stored at is the left-most bit, not the right-most.

Also I'd be interested in seeing what you were using just to see what your take on it was.

Monsieur OUXX

Quote from: monkey_05_06 on Tue 24/07/2007 17:06:18
Quote from: Monsieur OUXX on Tue 24/07/2007 16:24:02(the sign is stored in the last byte).
I assume you mean bit not byte...and by "last" I hope you mean left-most

Yes, i mean "left-most". I wrote "byte" because my error comes from the left-most byte, which actually contains the left-most bit.

Quote from: monkey_05_06 on Tue 24/07/2007 17:06:18
Also I'd be interested in seeing what you were using just to see what your take on it was.

I'm doing the same thing as you, but at "merge-time" i'm OR-ing* the four ints (the ones that get created by shifting chars) instead of ADDing them. I can't visualize why it causes an error, but since it works with an addition, it's a happy end.

*i mean the bitwise OR (|), not the boolean one (||)
 

monkey0506

#24
I'd forgotten that I had verified HoN's module to be malfunctioning when encrypting/decrypting videos (here). He did a good job with his module, it established the base for the one that I've written, there were simply some misconceptions that lead to invalid output corrupting the data. The module I've written clears up some of these and I've verified it to work successfully with video files.

I've also included functions for encrypting/decrypting files in the background. I did a background encryption/decryption operation of a sample video (~2322 KB) successfully at a rate of 1 KB per game loop in 4644 loops (2322 KB * 2-way operation) with no impact to speed. My tests show that attempting to decrypt more than this in a single game loop would result in a dramatic drop in game speed.

So Dualnames, as long as you can be absolutely sure that there will be one game loop run for every KB of data in your video, it may be possible to use encrypted videos in-game! The following example uses the module I've written, which I'll upload just the module for now, and try to work up some documentation over the next few days.

Code: ags
// encrypt operation -- do this prior to distribution
// game_start
EncryptedFile.Encrypt("shoryuken.wmv", "deadaswell", "shoryuken.scbr");

// repeatedly_execute_always
if (EncryptedFile.IsEncryptionComplete("shoryuken.wmv")) {
  File* video = File.Open("shoryuken.wmv", eFileWrite);
  if (video != null) video.Close();
  }


Code: ags
// decrypt operation -- do this in-game
bool VideoReadyForPlayback = false;

// game_start
EncryptFile.Decrypt("shoryuken.scbr", "deadaswell", "shoryuken.wmv");

// repeatedly_execute_always
if (EncryptedFile.IsDecryptionComplete("shoryuken.scbr")) VideoReadyForPlayback = true;

// time to play the video
if (VideoReadyForPlayback) {
  PlayVideo("shoryuken.wmv", 0, 0);
  File* video = File.Open("shoryuken.wmv", eFileWrite);
  if (video != null) video.Close();
  }


I've just reread your message about your personal results with a 45 KB file. Using this method that would translate to 45 game loops, taking barely over a second to decrypt! A 1 MB file would take 1024 game loops, or approximately 26 seconds.

Note that my module requires AGS 2.8 Beta 5. This is because I've been developing with it, and I internally use a dynamic array.

You can download my test game here (right-click, save as RAR archive).

You can download the module (no documentation) here (right-click, save as SCM).

Hope you'll find this useful. I've got to go to work for now. Maybe I'll get some documentation up soon...


SMF spam blocked by CleanTalk