Torque 2D/Asset Encryption
From TDN
|
|
[edit] IntroductionBefore 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.
|
|
[edit] Encryption AlgorithmsThe 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: [edit] 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. [edit] TwofishTwofish 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) [edit] BlowfishAnother 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. [edit] XTEAXTEA 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. [edit] KhazadStub: http://en.wikipedia.org/wiki/KHAZAD
|
|
[edit] Adding CipherLib to Torque2DTo 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. 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. |
|
[edit] Encrypting Files with the Cipher-toolThe 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
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.
|
|
[edit] Adding Support for encrypted PNG filesCipherLib 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.
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.
[edit] Support for other file formatsCipherLib 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 |
|
[edit] Implementing New CiphersThis 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: [edit] Step 1 - A new source fileActually 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).
[edit] Step 2 - The BlockCipher classIn 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:
};
[edit] Step 3 - The constructorThe 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;
};
[edit] Step 4 - Encryption / DecryptionThe 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;
};
[edit] Step 5 - Factory RegistrationThere 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);
/********************************************************************************************/
[edit] Step 6 - TestingThe 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
[edit] Step 7 - UsageNow 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();
}
|
|
[edit] CipherLib LicenseCipherLib comes under the zlib license which looks like this:
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.
|




