Torque 2D/Asset Encryption

From TDN

Contents

Introduction


Before you start using this library take some time to think about if you really need to protect your art assets. Neo Binedell's blog post and the comments on it might help you thinking :)

Some time ago Pat Wilson has created a resource called "Protecting Art Assets With Encryption". While it is pretty useful as a starter his CryptoStream is not able to handle block ciphers. However, block ciphers are the most common and most secure way of encrypting files. In summer I (Michael Woerister) created an extension to this: The AESStream class. It could read files that were encrypted formerly with an accompanying command line tool. It worked but I always felt it to be a bit clumsy and hacky.

Later I decided to give the whole thing a revamp and out came a small library with the core classes BlockCipher and BlockCipherStream that are built in way that everyone who likes can integrate his own block cipher easily.
Additionally there is the Cipher tool which is a SimObject and can be used to encrypt and decrypt files from TorqueScript. This is much more versatile than the command line tool. (One could think of integrating it in T2D's new Packaging Tool to automagically encrypt all with with a certain extension).

The library comes with some existing encryption algorithms. At the moment Blowfish, Rijndael (AES), XTEA, Twofish, and Khazad are included by default. They can be excluded easily. I have tried to use cipher algorithms and implementation that are free for commercial use. Some of them require you to include copyright statements (can be looked up in the source files). Please inform yourself before using a certain algorithm. The algorithms section of this article will give you some basic info on the included ones.

Here is what this library would be commonly used for:
- select one of the encryption algorithms
- encrypt your assets via a script written for the Cipher-tool
- read the encrypted assets via the BlockCipherStream class (this would typically be added transparently to the code like in Pat Wilson's resource)

Advanced users also can implement their favorite cipher via the BlockCipher base class.





Encryption Algorithms


The T2D CipherLib (just decided to give it this name) contains some well known block ciphers. All of them support ECB and CBC modes except for Khazad (but this should be easy to add). Here is some info on them:

Rijndael (AES)


This is the default option. As far as I know it is free for use. From rijndael.h by Szymon Stefanek:

Another implementation of the Rijndael cipher. This is intended to be an easily usable library file. This code is public domain.

It was chosen to be the Advanced Encryption Standard in 2000. Since then, there have been concerns for its security but all of the ciphers here should be more than secure enough for non-critical applications like asset protection in computer games. This cipher is very fast and very secure.

Twofish


Twofish is free, very fast and secure. From the Twofish inventor's homepage Bruce Schneier:

Twofish is a block cipher by Counterpane Labs. It was one of the five Advanced Encryption Standard (AES) finalists. Twofish is unpatented, and the source code is uncopyrighted and license-free; it is free for all uses.

(http://www.schneier.com/twofish.html)

Blowfish


Another algorithm by Bruce Schneier. It is a bit old but fast and secure. Please read the copyright statement in Blowfish.h if you want t use it. The implementation is free but you have to give credit to the creator.

XTEA


XTEA is a successor of TEA (Tiny Encryption Algorithm). It is quite fast but not faster than the others. The implementation is based on the code from http://en.wikipedia.org/wiki/XTEA . It is free for any use.

Khazad

Stub: http://en.wikipedia.org/wiki/KHAZAD






Adding CipherLib to Torque2D


To install CipherLib you have take the CipherLib folder that is contained in the zip-file and put it somewhere into "engine/source/" directory (or just "engine/" for the old directory structure) and then add it to your project. I have tested this in VC 2005 Express and it compiled fine. Add all files in all subfolder's to your project. Best you create the same folder structure in your project as on disk.
Image:CipherLibVCTree.png
When all files are added just recompile the project and you are done. You can now use the Cipher SimObject to encrypt and decrypt files or any of default BlockCipherStream classes ( which are AESStream, TwofishStream, BlowfishStream, RijndaelStream, XTEAStream, KhazadStream) by including "BlockCipherStream.h"

The file twofish2.c should not be included into
the VC project because it gets included by Twofish.cc. 
I did not have any problems when compiling it with 
the file in project but it is unnecessary.




Encrypting Files with the Cipher-tool


The Cipher-tool is easy to use. Here is a TorqueScript example of encrypting and decrypting a file:

$CipherTool = new Cipher();

// ENCRYPTION
$CipherTool.encryptFile( "example.png",                  // <-- the file you want to ENCRYPT
                         "password",               // <-- the encryption key ( case sensitive! )
                         "example_encrypted.png");      // <-- the name of the output file

// DECRYPTION
$CipherTool.decryptFile(  "example_encrypted.png",  // <-- the file you want to DECRYPT
                          "password",                  // <-- the key 
                          "example_decrypted.png");       // <-- the name of the output file


After this "example.png" and "example_decrypted.png" should be the same. Note that a file can get bigger through encryption, up to 15 bytes for AES. This is because the file has to be expanded if its size is not a multiple of the algorithms block size. This is called "padding". But it should not affect the quality of your files. For png-files it does not make a difference.

There are some additional methods for the "Cipher" object:

$CipherTool.setCipherName("name of a cipher");
$CipherTool.getCipherName();

This allows you to change the encryption algorithm that is used. Default is AES. By calling "printAvailableCiphers();" you get a list of all installed algorithms.

$CipherTool.setBufferSize( %new_buffer_size );
$CipherTool.getBufferSize();

This allows you to change the size of the memory buffer that is used when encrypting or decrypting a file. Default is 16 KB.





Adding Support for encrypted PNG files


CipherLib can handle encrypted PNG files nearly out of the box (see CipherBitmap.h/.cc). You have to do 2 things to fully enable it:

1) Registering with the Texture Manager

Go to dgl/gTexManager.cc, line 74 which should look like this:

static const char* extArray[EXT_ARRAY_SIZE] = { "", ".jpg", ".png", ".gif", ".bmp", "" };

Change the last entry to the extension you want the encrypted png files to have (e.g. ".xpng").

static const char* extArray[EXT_ARRAY_SIZE] = { "", ".jpg", ".png", ".gif", ".bmp", ".xpng" };

Unfortunatly there is no way to register a new extension in the TextureManager without modifying core code. But since this is only a very small modification we just do it.

2) Registering with the Resource Manager

Add a call to the script function "registerCipherPngExtension" *before* any t2dImageMapDatablock in script. For example right at the top of "main.cs":

...
//---------------------------------------------------------------------------------------------
// Enable profiling.
//---------------------------------------------------------------------------------------------
//profilerEnable(true);


//---------------------------------------------------------------------------------------------
// Enable encrypted PNGs.
//---------------------------------------------------------------------------------------------
registerCipherPngExtension(".xpng");

...

Alternatively you can modify game/main.cc. At approximatly line 120 there is a list of file extensions. Add yours below:


   ResourceManager->registerExtension(".bm8", constructBitmapBM8);
   ResourceManager->registerExtension(".dbm", constructBitmapDBM);
   ResourceManager->registerExtension(".png", constructBitmapPNG);
   ResourceManager->registerExtension(".gft", constructFont);
   ResourceManager->registerExtension(".uft", constructNewFont);
   ResourceManager->registerExtension(".dts", constructTSShape);

   // encrypted PNGs
   ResourceManager->registerExtension(".xpng", constructCipherBitmapPNG);
   
   Con::init();

and #include "CipherLib/CipherBitmap.h" somewhere at the beginning of the file.



Done
Now you T2D should be able to read PNG files encrypted with the Cipher tool. Be sure they got the extension you just registered and are encrypted with the same algorithm, key, and mode as specified at the top of CipherLib/CipherBitmap.h. Specify the filenames in your t2dImageMapDatablocks without extension. Then you won't have to modify them when using a ".xpng" instead of a ".png" as the Texture Manager iterates over all registered extensions.

Support for other file formats

CipherLib supports JPEG, BMP, BM8, GIF, and DBM as well. The procedure is the same. Be sure to change the EXT_ARRAY_SIZE when adding more file extensions to the texture manager. Example:

// valid texture extensions
#define EXT_ARRAY_SIZE 9
static const char* extArray[EXT_ARRAY_SIZE] = { "", ".jpg", ".png", ".gif", ".bmp", ".xpng", ".xjpg", ".xgif", "" };
static const char* extArray_8[EXT_ARRAY_SIZE] = { "", ".bm8", ".bmp", ".jpg", ".png", ".gif", ".xpng", ".xjpg", ".xgif" };

Instead of constructCipherBitmapPNG use:

constructCipherBitmapBM8
constructCipherBitmapBMP
constructCipherBitmapJPEG
constructCipherBitmapGIF
constructCipherBitmapDBM

or, if you are using the ConsoleFunctions to register the extensions in the Resource Manager

registerCipherBM8Extension
registerCipherBMPExtension
registerCipherJPEGExtension
registerCipherGIFExtension
registerCipherDBMExtension





Implementing New Ciphers


This is actually fairly easy on the side of CipherLib. As an example, here is how you would implement Blowfish if it was not already in there:

Step 1 - A new source file

Actually there is no need for a header-file. Instances of BlockCipher-derived classes can be created through a factory-class (but do not have to).

Step 2 - The BlockCipher class


In this case it will be named BlowfishCipher. Code:

// we have to include BlockCiphers.h as it contains the BlockCipher base class
#include "BlockCiphers.h"


// now we derive our own BlockCipher class from the base class:
class BlowfishCipher : public BlockCipher {
public:

     // nothing in there yet...

private:

};



Step 3 - The constructor

The BlockCipher base class must be initialized with some information that is crucial for the BlockCipherStream to work correct. The most important piece of information is the size of one block the algorithm can encrypt/decrypt (in bytes). In the case of Blowfish this would be 8 bytes, Rijndael and Twofish would have 16 bytes. The compiler will throw an error if you forget to do this. Additionally the BlockCipher constructor can be told the encryption mode (CBC or ECB - CBC is the default as it is more secure and hardly more costly).

Furthermore - for the factory to work - the constructor of our new class must have the signature (const char* key, const BlockCipher::Mode mode) .

Code:

#include "BlockCiphers.h"


class BlowfishCipher : public BlockCipher {
public:
    // Our constructor with base class initialization:
    BlowfishCipher(const char* key, const BlockChipher::Mode mode) :
        BlockCipher(8,mode) // <-- the base class constructor
    {
        /* We use some library calls here to create a valid key from the key-argument.
            In this example we use the SHA-2 algorithm to produce a 32 byte digest from
            the key-string. Blowfish can take a 32 byte key, so this guarantees we always
            get a valid key whatever length the input key-string has.
        */
        
        // get a buffer
        U8 sha_key[32];

        // make a key
	sha256((U8*)key,dStrlen(key),sha_key);

        /* In this example, like in CipherLib, the Blowfish implementation by Eric Young is
            used. You first have to initialize a certain key structure with BF_set_key before
            you can use the various encryption functions.
            We save this key structure as a private member, as it is reused for all encryption
            and decryption calls.        
        */
	BF_set_key(&mKey, 32, sha_key);


        // for CBC mode we need an initialization vector.
	for(int i=0;i<8;i++)
		mInitVector[i] = 0;
    }

private:
    // the key structure and initialization vector we need
    U8       mInitVector[8];
    BF_KEY    mKey;

};



Step 4 - Encryption / Decryption

The BlockCipher base class requires you to implement the two methods for encryption and decryption. This should be fairly straight forward. Code:

#include "BlockCiphers.h"


class BlowfishCipher : public BlockCipher {
public:
    BlowfishCipher(const char* key, const BlockChipher::Mode mode) :
        BlockCipher(8,mode)
    {
        U8 sha_key[32];
	sha256((U8*)key,dStrlen(key),sha_key);

	BF_set_key(&mKey, 32, sha_key);

	for(int i=0;i<8;i++)
		mInitVector[i] = 0;
    }

     /* What these method look like totally depends on the libraries you
         use. You also invent your own encryption. The only requirement
         is that "inNumBytes" from "inBuffer" are decrypted and copied to
         "outBuffer".
     */

     bool encrypt(const U8* inBuffer, const U32 inNumBytes, U8*outBuffer)
     {
          if(mMode == BlockCipher::ECB)
          {
                  U32 num_blocks = inNumBytes / 8;
                  for(U32 i=0; i < num_blocks; ++i)
                          BF_ecb_encrypt(const_cast<U8*>(inBuffer) + (i<<3),outBuffer + (i<<3),&mKey, BF_ENCRYPT);
          }
          else BF_cbc_encrypt(const_cast<U8*>(inBuffer),outBuffer,inNumBytes, &mKey, mInitVector, BF_ENCRYPT);
          
          return true;
     }

     bool decrypt(const U8* inBuffer, const U32 inNumBytes, U8*outBuffer)
     {
            if(mMode == BlockCipher::ECB)
            {
                     U32 num_blocks = inNumBytes / 8;
                     for(U32 i=0; i < num_blocks; ++i)
                           BF_ecb_encrypt(const_cast<U8*>(inBuffer) + (i*8),outBuffer + (i*8),&mKey, BF_DECRYPT);
            }
            else BF_cbc_encrypt(const_cast<U8*>(inBuffer),outBuffer,inNumBytes, &mKey, mInitVector, BF_DECRYPT);

            return true;
     }

private:

    U8       mInitVector[8];
    BF_KEY    mKey;

};




Step 5 - Factory Registration

There is a pluggable factory (see http://www.gamedev.net/reference/articles/article841.asp) coming along with the BlockCipher class. This class (BlockCipherFactory) can be used to create BlockCipher instances like this:

BlockCipher* someCipherInstance = BlockCipherFactory::create("Blowfish", "The Key", BlockCipher::CBC );

You only have to register the factory. This is done with the IMPLEMENT_CIPHER_FACTORY macro. Here's the code:

#include "BlockCiphers.h"


class BlowfishCipher : public BlockCipher {
public:
    BlowfishCipher(const char* key, const BlockChipher::Mode mode) :
        BlockCipher(8,mode)
    {
        U8 sha_key[32];
	sha256((U8*)key,dStrlen(key),sha_key);

	BF_set_key(&mKey, 32, sha_key);

	for(int i=0;i<8;i++)
		mInitVector[i] = 0;
    }

     bool encrypt(const U8* inBuffer, const U32 inNumBytes, U8*outBuffer)
     {
          if(mMode == BlockCipher::ECB)
          {
                  U32 num_blocks = inNumBytes / 8;
                  for(U32 i=0; i < num_blocks; ++i)
                          BF_ecb_encrypt(const_cast<U8*>(inBuffer) + (i<<3),outBuffer + (i<<3),&mKey, BF_ENCRYPT);
          }
          else BF_cbc_encrypt(const_cast<U8*>(inBuffer),outBuffer,inNumBytes, &mKey, mInitVector, BF_ENCRYPT);
          
          return true;
     }

     bool decrypt(const U8* inBuffer, const U32 inNumBytes, U8*outBuffer)
     {
            if(mMode == BlockCipher::ECB)
            {
                     U32 num_blocks = inNumBytes / 8;
                     for(U32 i=0; i < num_blocks; ++i)
                           BF_ecb_encrypt(const_cast<U8*>(inBuffer) + (i*8),outBuffer + (i*8),&mKey, BF_DECRYPT);
            }
            else BF_cbc_encrypt(const_cast<U8*>(inBuffer),outBuffer,inNumBytes, &mKey, mInitVector, BF_DECRYPT);

            return true;
     }

private:

    U8       mInitVector[8];
    BF_KEY    mKey;

};

/********************************************************************************************/

// the first argument is the name of the BlockCipher class
// the second is the name that gets registered in the factory.

IMPLEMENT_CIPHER_FACTORY(BlowfishBlockCipher, Blowfish);

/********************************************************************************************/



Step 6 - Testing

The ConsoleFunction testBlockCipherStream can be used to test your new BlockCipher class. It encrypts a file with the cipher tool and then decrypts it again via a BlockCipherStream using your BlockCipher. It then compares CRC values of the original file and the decrypted file. If the values are the same everything should be working correctly. This function can only be called from script!


// testBlockCipherStream( %cipher_name [,\"ECB\"|\"CBC\"], [%test_file], [%do_full_compare] );
//
// %cipher_name - The name that is registered in the factory
// optional ECB / CBC - The encryption/decryption mode
// optional %test_file - The file to use for the test. Default is "OpenAL32.dll".
// optional %do_full_compare - Not just CRC values but every single byte of the files will be compared
// e.g.

testBlockCipherStream( "Blowfish" );

testBlockCipherStream( "AES", "CBC" );

testBlockCipherStream( "XTEA", "ECB", "testfile.txt", true );

The function will then produce some output like this:


==>testBlockCipherStream("Rijndael");
BlockCipherStream Test Function:
Cipher = Rijndael
Mode = ECB
test-file's name = OpenAL32.dll
output-file's name = encodedOpenAL32.dll
-> file encrypted in 140 ms
-> file decrypted via stream in 50 ms
CRC of decoded file = 2844340396
CRC of original file = 2844340396



Step 7 - Usage

Now everything should be ready for using the BlockCipher class and a corresponding BlockCipherStream:


#include "CipherLib/BlockCipherStream.h"
#include "core/FileStream.h"

void DummyFunc(const char* fileName, const char* key, U32 numBytes, void* buffer )
{
   FileStream file;
   file.open( fileName, FileStream::Read)

   BlockCipher* cipher = BlockCipherFactory::create("Blowfish", key, BlockCipher::CBC);

   BlockCipherStream cipherStream( &file, cipher );

   cipherStream.read( numBytes, buffer );

   delete cipher;

   file.close();
}




CipherLib License


CipherLib comes under the zlib license which looks like this:

Copyright (c) 2006 Michael Woerister

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.

This means, you can use CipherLib for whatever you want but you do it on your own risk.