Game Development Community

dev|Pro Game Development Curriculum

Standard Zip Encryption Support

by Tom Bampton · 02/28/2006 (7:00 pm) · 47 comments

Download Code File

This resource adds standard Zip2.0 encryption support to Torque's existing zip support. This allows you to create encrypted zip files in any standard zip program and use them in Torque. You do not need any additional encryption program beyond what you already have, but command line zip software can be very useful for integrating this into a build system.

I wrote this code under contract for Andy Schatz, who has kindly given permission to post it as a resource. Many thanks also to Mathieu Lopez and James Urquhart for making sure it runs on a Mac.

The Code

Download the attached zip file for the meat of the code. It is all additions except for some minor tweaks to resManager.cc. I have included a complete resManager.cc from TGE1.4, but I suggest you don't use it directly. You can either use a decent merge tool like Beyond Compare or WinMerge to merge in the resource manager changes, or follow the instructions below.

The code will work on all versions of TGE, TSE and T2D.

resManager.cc

Note: These instructions are for TGE 1.4's resManager.cc, and should also be relevant to T2D. TSE's resManager.cpp will be slightly different but the code you need to change will be the same, just in a different place. Added or changed code is in bold.

At the top with the other includes, add an include for zipCryptStream.h. I put it after the other zip includes.

#include "core/zipSubStream.h"
#include "core/zipAggregate.h"
#include "core/zipHeaders.h"
[b]#include "core/zipCryptStream.h"[/b]

In ResManager::openStream() around line 858, find and modify the following code:

ZipLocalFileHeader zlfHeader;
      if (zlfHeader.readFromStream (*diskStream) == false)
      {
         Con::errorf("ResourceManager::loadStream: '%s' Not in the zip! (%s/%s)",
            obj->name, obj->zipPath, obj->zipName);
         diskStream->close ();
         return NULL;
      }

[b]      Stream *attachTo = diskStream;
      if(zlfHeader.m_header.bitFlags & 0x01)
      {
         // The zip is encrypted
#ifdef TORQUE_DEBUG
         Con::warnf("Loading Zip2.0 encrypted file %s from zip %s ...", obj->name, obj->zipName);
#endif

         ZipCryptRStream *cryptStream = new ZipCryptRStream;
		 cryptStream->setPassword(DEFAULT_ZIP_PASSWORD);
		 cryptStream->setFileEndPos(diskStream->getPosition() + obj->compressedFileSize);
		 cryptStream->attachStream(diskStream);
		 attachTo = cryptStream;
      }
[/b]
      if (zlfHeader.m_header.compressionMethod == ZipLocalFileHeader::Stored
            || obj->fileSize == 0)
      {
         // Just read straight from the stream...
         ResizeFilterStream *strm = new ResizeFilterStream;
         [b]strm->attachStream (attachTo);
         strm->setStreamOffset (attachTo->getPosition (), obj->fileSize);[/b]
         return strm;
      }
      else
      {
         if (zlfHeader.m_header.compressionMethod ==
            ZipLocalFileHeader::Deflated)
         {
            ZipSubRStream *zipStream = new ZipSubRStream;
            [b]zipStream->attachStream (attachTo);
            zipStream->setUncompressedSize (obj->fileSize);[/b]
            return zipStream;
         }
         else
         {
            AssertFatal (false,avar("ResourceManager::loadStream: '%s' Compressed inappropriately in the zip! (%s/%s)",
               obj->name, obj->zipPath, obj->zipName));
            diskStream->close ();
            return NULL;
         }
      }

Setting the Zip Password

The zip password is defined at the top of engine/core/zipCryptStream.h:

#define DEFAULT_ZIP_PASSWORD     "test"

You will want to change this. The same password will be used for all encrypted files. Please read the security section below for information on security.

Updating the projects

I have not included updated projects. It is assumed that you know how to use your development tools and can update the relevant projects yourself.

IMPORTANT NOTES ON SECURITY

There are a number of things to note in terms of security. Firstly, Zip2.0 encryption has some known weaknesses that allow a determined attacker to decrypt the zip if they have enough partial plain text. If you encrypt things like images, they will have standard headers that could provide sufficient known plain text for such an attack.

The password is #define'd in the header, and as such is recoverable by anyone who can use a disassembler. I would suggest that you use a password consisting of random gibberish so that it does not look like a password. If it looks too much like a password, then it will be easier to recover the password using a hex editor. As far as this code is concerned, you can use binary in passwords provided you do not use the NULL character. In reality, the characters you can use in the password will probably be limited by the zip application you're using to encrypt the zips.

Encryption is performed on a per-file basis. This means you can mix encrypted and unencrypted files in the same zip, but the ability to do that will also be limited by the zip application you're using. It also means that anyone can open your zip and see a list of files in the zip.

You may notice from the code above that when running a debug build, Torque will tell you when it's loading an encrypted file from the zip. This can be useful for debugging, but should probably not be enabled in release builds.

The code is sufficient for preventing casual copying of assets. It is possible to strengthen the security of the system, but that is a subject for another day.

File Support

When used with the TGE 1.4 resource manager (TGE 1.4, T2D 1.1), the code supports encryption of all files, including ogg files. Older versions of the resource manager (TGE 1.3, T2D 1.0, TSE MS3) do not support seeking withing compressed files and thus do not support loading of ogg files directly from the zip. The encryption code supports seeking in all cases.
Page «Previous 1 2 3 Last »
#1
02/28/2006 (8:47 pm)
as idea..
for online games may be useful to make
#define DEFAULT_ZIP_PASSWORD "test"
as a variable being set by scripts... and the value loaded from the server on connect...
#2
02/28/2006 (11:19 pm)
Clean, neat and well explained, just the way I like 'em.
#3
03/01/2006 (2:57 am)
This is absolutely the most important resource ever submitted on GarageGames.
There is nothing else out there, that is this functional and creates such a low impact on an existing system.
Thank you for a fanstastic resource.
#4
03/01/2006 (8:50 am)
Bank,

The problem with that is that if you zip and encrypt all the mods (which is possible) then the only script you can put the password in is the top level main.cs, which is plain text. That would make it even easier to recover the password :)

I do have some ideas for tidying up and securing the way passwords are specified, I just havent had time to do it yet.

T.

Edit: Using the main.cs itself as the password would be quite funny, since it's loaded into memory at game load and thus would be available. Would probably need a customized zipcloak app to use a password that long, but it would be interesting.
#5
03/01/2006 (8:57 am)
Quote:if you zip and encrypt all the mods (which is possible)
yeah I know... but assuming if you keep un-encrypted only files needed to connect to the server and all the rest - zipped with pass... so, the client gets the password from server and use it for loading all the rest content of the game.. this can work..
may be I'll do exactly this thing for our project.. we'll see
#6
03/02/2006 (9:21 am)
Thanks a lot for this resource, very useful! Good job!
#7
03/02/2006 (11:36 am)
Woo! Problem solved forever. Thanks! :)
#8
03/04/2006 (5:26 am)
Such an easy implementation! Good work!
#9
03/12/2006 (7:22 am)
Here is a zip archive containing the updated files based on this resource for the latest TSE build.

http://www.thzero.com/programming/torque/9896.zipcryptresourcev1-TSE.zip
#10
03/16/2006 (8:15 am)
I know a thing or two about cryptography (I haven't studied the underlying math, but I've made use of various cryptography libraries on several occasions). Allow me to reiterate the "Important notes on security".

Zip's standard password protection is a joke.

It is crackable, it has been cracked, there are tools available on the internet to crack them:

http://www.filesland.com/download/decrypt.html
http://www.password-crackers.com/en/category_98/

Those are just a couple of the links that turned up when I googled for "zip password crypto".


Having said that, WinZip9.0 supports the American Encryption Standard (AES). This is NOT crackable, and has to be broken through "brute force"... in other words: "spend several years of computer time trying out all the possible passwords until you hit on the right one". AES is what the government uses to secure its files. Not Fooling Around.

But it doesn't sound like this resource uses AES (or it wouldn't say you can use any ol' zip utility).


Don't get me wrong, it'll keep out at least 90% of your users, but it really only takes one to scatter your script all over the internet.

The sad truth of this situation: Even if you use strong crypto, the password has to be somewhere on that computer in order to get your own files open. And that means someone can dig it out and open up your files. Even when the password looks like random gobbledeegook burried in a pile of instructions, your app can be disassembled and stepped through to find out where you got the password, plus any changes you made to it (XOR FF00FF00 or whatever) before you used it.

You MIGHT be able to keep out even more folks by having your game download that password through a SSL connection every time the game starts... but I'll wager someone could figure out a way around that too (sneak their own SSL library in to replace the one on the system for example)... and heaven help you if your password server went down. Many Angry Users.

That's the sate of things until DRM is built into hardware... but DRM is rife with potential abuse, and I'm not looking forward to that day at all. When it comes, we loose the ability to control what runs on our own machines.
#11
03/16/2006 (8:37 am)
Mark,

I mentioned that in the resource. Under the "IMPORTANT NOTES ON SECURITY" section:

Quote:There are a number of things to note in terms of security. Firstly, Zip2.0 encryption has some known weaknesses that allow a determined attacker to decrypt the zip if they have enough partial plain text. If you encrypt things like images, they will have standard headers that could provide sufficient known plain text for such an attack.

Incidentally, I also have WinZip 9/10 compatible AES support going in Torque. I didn't include it in this resource because it's still got a bug when seeking in encrypted streams, which is needed for loading oggs and uncompressed files. Both of which are problems if you just zip starter.fps.

The weakness with any asset protection system is the method in which the password is stored, not the encryption algorithm used. In this resource, the password would take a skilled attacker less then 30 minutes to retrieve, regardless of the encryption algorithm. The best you can extend that to is 5 or 6 hours with clever obfuscation of the password. There is just not enough gain to make it worthwhile implementing.

The point of this resource is to provide workable asset encryption to protect against casual theft. It does this sufficiently and inobtrusively. However, it is easy to extend should someone be paranoid enough to want to do so. Personally, I dont think the gain is worth the extra development time.

T.

Edit: Bah, you already edit your post to answer your own points :/
#12
03/17/2006 (8:02 pm)
Wouldn't it be possible to put the password in C++? I haven't looked at this solution at all to know the answer to that question, but it is worth looking into.
#13
03/18/2006 (6:18 am)
Bah, long post was just swallowed, too lazy to type it again.

What gives with this damn post bug?
#14
03/20/2006 (12:39 am)
@Tom: Don't ge me wrong. This is handy... but at least one of the people up top sounded like they thought this was the "end-all be-all". We both understand the situation. They didn't, and I was trying to explain it to them. I felt my first cut at that explanation was too harsh, so I made the changes you note.

@Anton: If a password is on someones computer, they can find it. "Disassemblers" can turn a compiled program back into c/c++ code... The variable & function names are lost (replaced with something like function1, variable1, whatever)... but all the data is still there... and, as Tom mentioned, "a skilled attacker" will know what to look for, and WILL find it. It's only a matter of time.

Even if your "password" is some sequence of bytes yanked right out of your executable code (rather than stored in a string), then XORed, bit-shifted, and otherwise frobnicated, a skilled attacker won't be stopped. Slowed? Sure. Stoped? Absolutely not.


Oh... what if... nope. Drat. Here's the idea anyway (and the torpedo):
Idea: Write a program to write 1000 or so different decryption attempts. All but one fail quickly (you don't want to impact load time too much). This would force an almost-clever attacker to step through LOTS of code... hopefully so much that they'd give up in frustration.

Torpedo: Divide and conquor (that isn't spelled right). They set their breakpoint at the half-way point. If it's reached, they know which half of your list of decryptors the right one is at. Keep halving the list, and then you're looking at 10 checks instead of 1000.

Torpedo defense: Randomize the list of decryptors every time. They could still spike your random number generator, giving themselves a fixed list to divide/conquor again.. Hmph.

See what I mean? It's a no-win situation for the 'white hats'.
#15
03/30/2006 (7:03 pm)
Main problems with this is easy password recovery by hacker.
But there are such tools as HardLock, ASProtect and EXECryptor(or even StarForce variants) which not only 'copy protect' code but also protect program code from debugging by hacker. Yes, they CAN be cracked, but it's not too easy(especially Star Force).
#16
04/06/2006 (1:28 pm)
When trying to add this resource, I get the following:

Error	3	error LNK2019: unresolved external symbol "public: void __thiscall ZipCryptRStream::setPassword(char const *)" (?setPassword@ZipCryptRStream@@QAEXPBD@Z) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	4	error LNK2019: unresolved external symbol "public: __thiscall ZipCryptRStream::ZipCryptRStream(void)" (??0ZipCryptRStream@@QAE@XZ) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	5	fatal error LNK1120: 2 unresolved externals	d:\LastStraw\ForbiddenDawn\trunk\Development\Source\tools\buildWad_DEBUG.exe	1	
Error	7	error LNK2019: unresolved external symbol "public: void __thiscall ZipCryptRStream::setPassword(char const *)" (?setPassword@ZipCryptRStream@@QAEXPBD@Z) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	8	error LNK2019: unresolved external symbol "public: __thiscall ZipCryptRStream::ZipCryptRStream(void)" (??0ZipCryptRStream@@QAE@XZ) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	9	fatal error LNK1120: 2 unresolved externals	d:\LastStraw\ForbiddenDawn\trunk\Development\Source\tools\map2dif_plus_DEBUG.exe	1	
Error	11	error LNK2019: unresolved external symbol "public: void __thiscall ZipCryptRStream::setPassword(char const *)" (?setPassword@ZipCryptRStream@@QAEXPBD@Z) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	12	error LNK2019: unresolved external symbol "public: __thiscall ZipCryptRStream::ZipCryptRStream(void)" (??0ZipCryptRStream@@QAE@XZ) referenced in function "public: class Stream * __thiscall ResManager::openStream(class ResourceObject *)" (?openStream@ResManager@@QAEPAVStream@@PAVResourceObject@@@Z)	engine_DEBUG.lib	
Error	13	fatal error LNK1120: 2 unresolved externals	d:\LastStraw\ForbiddenDawn\trunk\Development\Source\tools\map2dif_DEBUG.exe


Thoughts?
#17
04/06/2006 (1:30 pm)
You didn't add the files to the TorqueLib project.
#18
04/06/2006 (1:32 pm)
Hmm. Is every file in Torque main supposed to go in torque lib too? Not just in relation to this resource, but in relation to anything i do/add/change?
#19
05/05/2006 (9:00 pm)
On this idea, even if a user gets into the graphics and what-not, if they replace a texture, will it appear replaced on the map as well, or will the server recongize that their version of the texture is different and disallow connectivity?
#20
06/21/2006 (1:20 pm)
The only problem I'm having with this resource is that it adds a noticeable performance hit to the game (using TGB). Especially the initial loading of assets, which is about 5x as long with an encrypted zip file. Anyone else have this issue? If so, were you able to figure out why and fix it? :)
Page «Previous 1 2 3 Last »