Does anybody know about best practices to hide resources images in an iphone app. For instance, hiding atlas png's from tools like File Juicer?
Hiding resources in an app
(47 posts) (16 voices)-
Posted 1 year ago #
-
I'm not an expert on the subject, but are static libraries more secure than regular code? This is something I thought about too.
Posted 1 year ago # -
I looked into this and even asked on this forum but it seems not many people were bothered by it.
http://www.cocos2d-iphone.org/forum/topic/4451
I'd liked to have protected everything not just images. I had a look at PhysFS - http://icculus.org/physfs/ which is nice but a well known archive system (not much protection). You really need some form of encryption to confuse juicer.
SIO2 has built-in asset protection with encryption but its been built from the ground up to use their serialisation routines which is why they can do that. Not so easy with cocos2d because of the mixture of built-in and apple API loading routines.
It might not be too hard to put something in, PVR's are loaded as straight data and then unpacked, anything else is loaded using apple's [UIImage initWithContentsOfFile]. If something sits between the two which reads the data from an archive then it may be possible. It would be easy for PVR's but for other formats you would need a routine to convert data into a UIImage (may be already possible).
Posted 1 year ago # -
@mhussa: it shouldn't be that difficult to implement something like that in cocos2d, at least for the images.
You need to subclassCCTextureCache. eg:-(void) addImage:(NSString*)filename { if( extension is crypted ) { // key: the secret key. You should initialize the the CCTExtureCache with this key // decryptImage could be any symmetric algorithm... I would chose the fastest symmetric algorithm. mem = decryptImageusingKey( key, filename ); // now you have to create a texture from mem UImage *image = [[UImage alloc] initWithData:mem]; // create the texture using CCTexture2D [image release]; } else { [super addImage:filename]; } }Posted 1 year ago # -
@riq: well ok, that was simpler than I'd expected. I'd really like to protect my TMX maps to prevent idle tinkering. Thankfully the parser can use a data block as well as a URL.
Sigh, just when I thought I was finished. I'll take a look to see whats possible.
Posted 1 year ago # -
Thanks!
@riq: That's a good idea just subclassing CCTextureCache...
Posted 1 year ago # -
I've made a first attempt, I've integrated it into Lam's demo for people to see (hopefully he won't mind). Download from here:
http://www.gametakeaway.com/temp/CCRadialTransitionDemo.zip
The demo will run exactly as before except the Icon.png is encrypted. Build it and then take a look under "products". right-click the ".app" and select "reveal in Finder". In the finder select "show package contents" and take a look at Icon.png.enc (or try to juice it out). It currently works on any texture passed through the texture cache (which should be all). I've tested PVR and PNGs, both work on the simulator and device.
I've added a new build phase called "Encrypt" which is the last phase. It runs a script to encrypt the files you specify within the script. It calls a small utility which encrypts each file and stores the resulting file with a .enc extenstion in the .app dir, then removes the original file copied into the .app by the copy resources phase.
Doing it this way means the images stay usable during development but are encrypted when built into the final target. The encryption can be disabled my simply commenting out the "encryptFiles" variable in the run script.
The cocos code and the encrypter share the same encryption code and key, both are stored in one place (CryptUtils.cpp).
For those who are interested in this, can you take a look and review it to see If I may have missed something.
Modified Files:
- CCTextureCache.m, creates a CCEncryptedCache based on a #define
- CCTexture2D.m and .h, added PVR init with data method.
- CCPVRTexture.m and .h, added PVR init with data method.New files:
- CCEncryptedTextureCache.m and .h, subclass of CCTextureCache
- CryptUtils.cpp and .h - encrypt/decrypt code, this code has the XXTEA 128bit algorithm (small+fast) can be easily swapped out.
- cc_encrypt.cpp and makefile - command-line utility used by the build phase to encrypt files and store into the final package. built once externally in the project root directory (just type "make").Using it is easy but setting it up needs some explaining which I'll come to. Just wanted to get some feedback first.
Posted 1 year ago # -
I've had to pull it off the web, found a serious bug. Service will be resumed shortly.
Posted 1 year ago # -
Phew! its ok, false Alarm.
I changed the crypt key and didn't rebuild the encrypter (slaps wrist).
demo is back on the web.
NOTE:to rebuild the encrypter, open a terminal window in the project root. remove cc_encrypter, then type 'make'.
P.S: Get your random crypt keys from here:
Posted 1 year ago # -
Does it have a performance impact when pngs are not optimized by xcode?
Posted 1 year ago # -
No easy answer for that one, you need to try it. How are you disabling PNG compression for your resources?
The included XXTEA algorithm implementation is very fast, its 32-bit optimised. The input file is aligned on 4 byte boundaries which is why you may see a few bytes added to the output file. The encrypted data is read into memory and decrypted in-place, so no copying.
I've tested it on all my graphic resources and I can't see any performance impact at all. Although I use PVR's and PNGs for the UI so I don't have any 1024x1024 PNG's to try it on.
I'll add a few tests to the demo with timings.
Posted 1 year ago # -
Posted 1 year ago #
-
The only side effect I've noticed is that after compressing the .app dir final package compression decreases by a tiny amount. In my case it went from 3.9MB to 4.1MB. The encryption probably introduces more variation into the data but not significant amounts.
Posted 1 year ago # -
You know how when you submit an app, Apple asks if you have any encryption...does this change your answer to that question? Just curious...
Posted 1 year ago # -
^ good point.
I think they ask because of US export regulations and they are probably more interested in 1024+bit weapons grade encryption. 128bit encryption to stop casual browsing may not be at the top of the list. I'll trying to submit with it soon (after adding IAP), then we'll see.
The appstore does already have 256bit encryption apps.
Posted 1 year ago # -
I've been googling a few threads and this one seems interesting:
http://blog.vladalexa.com/2009/07/07/appstore-and-cryptography/
adding encryption > 64bits needs an export license if you intend to sell outside the US.
In this example the app decrypts at runtime never encrypts, so not sure about the policy.
but there's hope, if you use apple's CCrypto routtines then you're export ready. I'll look into it.
Posted 1 year ago # -
@mhussa:
We all know that it will still be possible to obtain the uncrypted images. It doesn't matter if you use a 2048 bit key or a 16-bit key.So, if there is an issue with the export regulation, I would use, for example a ROT-13 algorithm, or just a simple XOR.
For example a XOR algorithm will be really fast (probably the fastest one), people with File Juicer won't be able to obtain the images, and there won't be any issue with Apple's export policy.Posted 1 year ago # -
OK I've updated the encryption/decryption side of things to use Apples CCCrypt API. It uses the AES128 algorithm with a 256bit key.
download from: http://www.gametakeaway.com/temp/CCRadialTransitionDemo.zip
Details about what apple expects on submission are here (login required).
https://devforums.apple.com/message/7521#7521
When you submit your app say "no" to the encryption question because it only applies to your own custom encrypt/decrypt code. The apple crypt API is already licensed for export. One of the users on the same thread submitted an app in this way and was approved.
I'm going to leave it at that for the moment.
If you use this, Just remember to change the crypt key from the default and rebuild the command line encrypter (see above).
P.S. I saw no perceivable drop in performance testing on the device.
Posted 1 year ago # -
@mhussa
Wow! thanks for the effort and work put into researching and implementing the encrypter/decrypter.The contributions that I've seen popping up on this forum to help the community is awesome.
*I'm grinning from ear to ear as I type this.* :D
I've been busy so I haven't had much time to check out the forums much but I'll look at the demo since I know very little about encryption in general and any start is helpful.
Posted 1 year ago # -
Thanks Lam and for the demo's you've given out to others.
Try it out, and let me know what you think.
If you want to experiment, Just add another image as usual and then add the filename to the "Encrypt" run script. The build output will show the script output.
Posted 1 year ago # -
Cool, I think it probably has minimal impact because of the texture caching it only needs to decrypt once for most cases.
Posted 1 year ago # -
Looks like its not a minimal impact when using non-Xcode optimized pngs. Our game runs notable slower on older hardware with the encrypted pngs.
Posted 1 year ago # -
Thats a shame, those PNGs must be big.
c'est la vie.
Posted 1 year ago # -
Just a quick followup to thank your for sharing this code.
I used it to encrypt XML files that make up levels, to avoid evil doers tinkering with those and being able to screw up the high scores list.
For anyone interested, all I had to do was use the tinyxml libraries and include cryptutils by mhussa.
If you've ever dealt with the NSXmlParser and didn't like it (...) I suggest you try out tinymxl.Example code:
TiXmlDocument doc; NSString *encPath = [[NSBundle mainBundle] pathForResource:fileName ofType:@".enc"]; NSMutableData *encData = [NSMutableData dataWithContentsOfFile:encPath]; unsigned char* buffer = (unsigned char*)[encData mutableBytes]; CCDecryptMemory(buffer, encData.length); doc.Parse((const char*)buffer, 0, TIXML_ENCODING_UTF8);That's all it takes to load in an encrypted xml file.
Cheers!Posted 1 year ago # -
I also use it to encrypt/decrypt my TMX maps, insert the following into CCTMXXMLParser.mm file initWithTMXfile method:
NSString *encFile = [[NSBundle mainBundle] pathForResource:tmxFile ofType:@"enc"]; if ( encFile ) { NSMutableData *encData = [NSMutableData dataWithContentsOfFile:encFile]; CCDecryptMemory([encData mutableBytes], encData.length); parser = [[NSXMLParser alloc] initWithData:encData]; } else { NSURL *url = [NSURL fileURLWithPath:filename_]; parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; }Its that easy.
@PatrickC: Thanks for the tinyxml mention looks useful.
I have noticed zero times on my Openfeint leaderboards. I think they're searching the binary for the product code and then writing their own submit hiscore code to get their names up there (or some other nefarious reason). It might be wise to encrypt and then decrypt that kind of information before registering with openfeint, ad services, etc etc.
Posted 1 year ago # -
mhussa,
Thanks for sharing your code and expertise in this area. I downloaded the sample and it all looks pretty straightforward, except I cannot seem to locate the new build phase called "Encrypt". Is it not in the demo program or am I missing something obvious?
Q
Posted 1 year ago # -
^riq had written a nice blog post which should point you in the right direction:
Posted 1 year ago # -
mhussa,
Thanks for the tip, that helped alot.
Sorry if this a newbie observation/question, but there is one part of the code I am wondering about...
In the CCEncryptedTextureCache, you are doing this:
NSMutableData *encData = [NSMutableData dataWithContentsOfFile:encFile]; CCDecryptMemory((unsigned char *)[encData mutableBytes], encData.length);but inside of of CCDecryptMemory (as well as in CCEncryptMemory), you are allocating a slightly larger buffer:
size_t bufferSize = length + kCCBlockSizeAES128; void *buffer = malloc(bufferSize);and then copying the larger buffer over the smaller buffer (data) right before the return:
memcpy(data, buffer, numBytesDecrypted); //copy the decrypted data over the original dataIt has me wondering if this has the potential to corrupt memory since you would (in some cases, when numBytesDecrypted is equal to bufferSize) being writing beyond the size of the input buffer?
I thought that perhaps if this was an issue that it might be solved by doing this in CCEncryptedTextureCache:
NSMutableData *encData = [NSMutableData dataWithContentsOfFile:encFile]; // Increase encData to accommodate max output buffer size of CCDecryptMemory [encData setLength:[encData length]+kCCBlockSizeAES128]; // Pass the unadjusted length into CCDecryptMemory CCDecryptMemory((unsigned char *)[encData mutableBytes], encData.length-kCCBlockSizeAES128);Granted this is not the most elegant solution (assuming there actually is a potential issue), but it was the first thought that crossed my mind. In any case, I am mainly wondering if there is really an potential issue here or am I missing something?
Thanks,
Q------------------------------------------
UPDATE: Also, I am finding that it seems be important to return the numBytesDecrypted on success instead of just returning zero, since it seems that for certain data types after a encrypt or decrypt call the follow needs to be done:
int newSize= CCDecryptMemory((unsigned char *)[encData mutableBytes], [encData length]-kCCBlockSizeAES128); // We re-adjust the encData to the actual number of bytes decrypted [encData setLength:newSize];for tilemaps there does not seem to be an issue, but for other data types (like plists) setting the newSize appears to be crucial. At least as far as what I have encountered in my code. Again, feel free to correct me if I am wrong. And thanks again for sharing your knowledge and code.
Posted 1 year ago # -
@questor: yes you're right, it may overwrite the end of the nsdata data length. The only theory I have that it hasn't triggered any faults is that the nsdata or heap memory is allocated in 16 byte blocks so you get some extra space allocated even though you asked for 1 byte (as an example). The other thing is that the crypt functions may be encoding/decoding 'length' bytes which means no overflow.
I'm personally not keen on adding fixups in the client code, it would be better to fix at the encrypt/decrypt side to prevent repeating the same code. I'd say a quick fix is to memcpy the input 'length' instead of numBytesDecrypted, need to try it.
The reason plists don't work is that the plist is put into a special binary format when packaged, the encryption will also add the end block. The plist reader thinks there's more data read at the end of the original data and then fails to interpret the end block returning nil. I had to adjust to the source size as well to make it work.
Try it using memcpy of the input length and let me know. Thanks for spotting that.
Posted 1 year ago #
Reply »
You must log in to post.