Click here to Skip to main content
Click here to Skip to main content

A simple set of classes to encrypt data

, 4 Nov 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
A simple set of classes to encrypt data

Introduction

Some years ago my now wife and I launched an IRC based trivia game site. I did the programming, (game client and matching game-aware chat client) and she did the web-site. Together we did the human related admin. That meant, among other things, selecting (or coaxing Smile | :) ) volunteer game hosts to write and run games.

IRC Networks - a matter of trust

IRC networks, like much of the internet, attract their fair share of nut-cases; people who will hide behind the 'anonymity' of an IP address and be as disruptive as possible. So it becomes necessary to invest game hosts with the power to gag, kick or ban disruptive players.

This is achieved by connecting to the IRC network and supplying the appropriate password. (Appropriate because there are differing levels of authority on most IRC networks, where the level of authority is specified by the password used). Most chat clients facilitate this by expecting the user to type a command such as /join #chatroom password. The chat server then assigns the appropriate level of authority based upon the password supplied.

Our IRC network was a little different. Most IRC networks have many chatrooms covering various topics where each chatroom has it's own little 'clique' of hosts and it's own rules governing conduct. Our server, given that it was dedicated to gaming, had a more restricted selection of chat rooms and consistent rules of conduct applying to all chat rooms. More importantly, each host on the 'classic' IRC network knows the hosting password(s) for each chatroom in which they are a host. This is a major weakness. If the password escapes into the wild (and such an escape need not be malicious, it can be as simple as mistyping the /join keyword) it can take a major effort of co-ordination to change the password. All legitimate users must be identified and updated. There is also almost always a lingering suspicion that password leakage was deliberate.

Avoiding the need for trust

I decided to design a model for my chat client where the host had no need to know the hosting password(s). To achieve this I needed to come up with an encryption scheme that met the following goals.
  • No passwords should be saved anywhere in plain text.

  • It should be difficult to reverse engineer.

  • It should be time limited. In other words, the encrypted data used by the chat client should require periodic updates.

  • It should be able to detect if someone set the PC clock back in time.

  • Last but not least: It should be something I could code, debug and test. I'm not a cryptographer!

The code and utility I present here is based on the code I used in the game software. Note however that it's not exactly the same code and I certainly won't be presenting the same encryption keys. (The site is still live as of writing even though I'm no longer involved in it).

Abstraction

Let's now abstract away from IRC networks and take a higher level view. Expressed again, differently, these are my goals.
  • Encrypt some text of interest to the application which should be obscured.

  • Make it difficult to decrypt without knowledge of the specifics.

  • Set a time limit including the ability to detect if the PC clock has been set backwards in time.
For the sake of convenience for myself I added a fourth requirement. The encrypted entity should be an MFC archive. This meant that the encrypted entity had to be a CObject derived object that implemented the DECLARE_SERIAL and IMPLEMENT_SERIAL macros. I make no apology to thee or thine for using the MFC serialisation mechanism. It allows me to easily save and reload complex data structures.

The CObject derived object is serialised into a memory archive which is then encrypted and written to storage. Loading reverses the process: the encryped entity is loaded into a memory archive, decrypted and then serialised into an object instance you supply.

The scheme

The scheme I came up with is a triple XOR of the serialised block of memory against three different encryption keys. In addition the encoded entity contains two timestamps. The first timestamp is when the entire encoded entity expires. The second timestamp is the last time we decoded the entity. This implies that each time the entity is decoded we update it by rewriting the second timestamp.

The second timestamp (the burn date) is used to protect the first timestamp (the expiry date). If the PC clock time is earlier than the burn date then we know something is fishy.

Aha you say! The burn date changes each time we run the program. If I know there's a timestamp that says when we last decoded the entity then I can decode the burn date and, more importantly, rewrite it, and defeat the ability to determine that the PC clock has been set back in time.

Well, yes, you could, if you could find where the burn date is. So I added another variable. The very first value written to the encoded entity is the lower 32 bits of the value returned by a call to QueryPerformanceCounter(). Then, after the triple XOR of the entity against the three encryption keys I do a final XOR of the remainder of the entity (excluding the initial 32 bits) using that initial 32 bits. Thus, after each decoding of the entity the entire entity (as written back to it's storage) changes its byte values. It's not easy to detect that the only substantial change was the burn date when the entire entity changes.

Simple but effective. If you know that the first 32 bits of the encoded entity is the first level encryption key then you can always decode the remainder of the entity back to a standard state.

Does that get you very far? Not really. You still have a block of apparently random binary values.

Aha you say! The exe file I'm cracking must contain the decryption keys. I have an implementation of the UNIX strings utility handy, which will let me dump all the strings contained in the exe. Surely the decryption keys must be in there somewhere.

Well yes, they are. But as I've already demonstrated, I'm a devious bastard. The encryption keys are also encrypted into the exe. For this to be difficult to crack I use a random sequence of characters as a meta key. These characters are used as a decryption key to decrypt the keys that will be used to decrypt the encrypted entity in storage (after it's been decrypted using the timestamp). Holy crypt!!!

I've provided a simple dialog based utility (MetaKeyGen) to create meta encryption keys given a meta key and a desired key. The result is a C code string, representing the desired key encoded by the meta key, which you cut and paste into your source file.

As a final safety measure (yeah I know I'm paranoid but have you ever had to rescue an IRC network taken over by hostile users? I haven't either, maybe because of this paranoia) the classes presented here store only the encrypted entity. Each time a caller requests data the data is returned after all internal buffers have been overwritten with random values and freed. I can't prevent a debugger seeing the contents of process memory but I can make it more difficult for a cracker. Paranoia carries a performance penalty but it's all done in memory so it's not that bad a penalty. The random value I use is the least significant byte of the return value from a call to rand(). Thus, each time the program is run the temporary storage it needs is overwritten with different values.

Do note that the expiry date and burn dates referred to earlier are in fact members of the CObject derived class and it's your objects responsiblity to determine appropriate action if they are found to be invalid.

The classes

CEncryptedData is the class that stores the encrypted entity. The encrypted entity is simply a block of BYTES that contain the text to be protected. The class provides constructors to Load, and Save functions to act on both the registry or a disk file and it also implements the QueryPerformanceCounter() obfuscation.
class CEncryptedData
{
    friend class CDataDecrypter;
public:
    CEncryptedData(LPCTSTR szFileName);
    CEncryptedData(HKEY baseKey, LPCTSTR szPath, LPCTSTR szKey);

    ~CEncryptedData();

    BYTE *Data(DWORD& dwLen);
    void Attach(BYTE *pbData);

private:
    void Save();

    void    Obfuscate();
    BYTE    *m_pbData;
    DWORD   m_dwLen;
    HKEY    m_baseKey;
    CString m_csPath,
            m_csKey;
};
The Data() function returns a pointer to an encrypted block of BYTES that still needs to be decrypted using the triple XOR scheme.

CDataDecrypter is a class that provides the core functionality of the en/de crypter.

class CDataDecrypter
{
public:
    CDataDecrypter();

    void Decrypt(CEncryptedData *pbData, LPCTSTR szMetaKey, CObject *pObj);
    void Encrypt(CEncryptedData*& pbData, LPCTSTR szMetaKey, CObject *pObj);

private:
    void Scramble(BYTE *pbSrc, BYTE *pbDest, LPCTSTR szKey, int iLen);

    static BYTE key0[];  //  these are the strings returned by 
    static BYTE key1[];  //  metakeygen and compiled into your
    static BYTE key2[];  //  app
};
The Decrypt() function takes a pointer to the encrypted entity (after it's been de-obfuscated), a meta key that is used to decode the three encrypted keys, and a pointer to a serialisable object derived from CObject that contains your data. Debug builds of the class assert that the object pointer passed is in fact serialisable.

key0, key1 and key2 are the BYTE arrays created by MetaKeyGen and are compiled into your exe file.

CEncryptedData doesn't require that the storage exists at the time the class is instantiated. It does the 'right thing' by failing benignly. This is the mechanism by which you create a new instance of the storage. Call the appropriate CEncryptedData constructor with the name of the desired storage, even though the storage does not yet exist, and then call CDataDecrypter::Encrypt to encrypt your object and write it to the desired storage.

The classes here overwrite internal buffers before returning. However they can't overwrite your decoded object. It's up to you to manage the lifetime of your decoded object. Decode the object, use it's data, and destroy it (and overwrite memory) as soon as possible after the decoding. Your CObject derived object should at least zero all allocated memory in it's destructor before freeing the memory.

The classes are not threadsafe. This only matters if one thread is attempting to decrypt your encrypted entity at the same moment another thread is attempting to do a save of the same entity. The Save() function updates the first 32 bits of the encrypted entity and obfuscates it, writes it to storage and then obfuscates it a second time to reverse the effect of the first obfuscation.

Bad things to do when using this scheme

  • Don't imagine that this scheme can provide copy protection. It can't. At best it can be used to sign a copy of some software with a unique ID that identifies a particular licensee. If suddenly your software escapes into the wild through warez this scheme can be used to identify the leaker. Of course this only works if you distribute a unique encrypted entity with each copy that identifies the recipient of that copy.

  • It's tempting to add validation functions to the class so your code can verify a correct decryption. But if you do that then you're providing an attempting cracker with a way of validating his/her success at cracking your scheme. After all, your code is making a call into some unknown function and testing the result. It then branches to code that pops up a message box with a message that decryption failed. Crackers look for those strings in an exe and work out where they're referenced. From there it's a short step to breaking your protection scheme wide open. Within the scenario for which I developed theses classes a validation check isn't necessary. At worst what comes back is an invalid date or the wrong password.

  • At first it seems to make sense to validate licensed software during application startup. A naive check would decrypt the encrypted entity via a call from CWinApp::InitInstance(). Bad idea! Remember that if you're trying to protect your software you are also trying to defeat crackers. It's not that difficult to locate the entry point of an exe file and single step, using a debugger, through the startup code.

  • For the same reasons, it's not a great idea to try and present information from the encrypted entity in an About box. Any developer worth his salt can locate the invocation of the About box and reverse engineer from that point.

How do I get around the last two points? You could set a timer during application startup to send a validation command via PostMessage() to your main message loop. Sometime after application startup the app validates itself and acts appropriately if validation fails. Similarly, in your About box OnInitDialog() procedure you could post yourself a message or set a timer to update UI elements some time after the WM_INITDIALOG message is processed. But best, perhaps, is not to validate your encrypted entity until you need the information encrypted within it.

Conclusions

Is my scheme unbreakable? Of course not! Only a fool imagines he can create something that someone else can't break. But in the five years this scheme has been in use we've never had a security break on our IRC network. This could, of course, merely be due to the fact that no one has tried. I'll never know.

What use is it outside the scenario for which I designed it? I honestly don't know. It could be used to protect FTP passwords for, for example, a blog client. Or it could be used to uniquely sign a copy of some software licensed to a specific person. Whatever, it can stand as a monument to my paranoia Smile | :) I do however use these classes in the next article I'm writing. <!------------------------------- That's it! --------------------------->

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rob Manderson

United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.
 
I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.
 
Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.
 
Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.
 
I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.
 
Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
GeneralAbout the security of this PinmemberAJR_UK7-Nov-06 13:50 
GeneralRe: About the security of this PinmemberAJR_UK7-Nov-06 14:08 
Questiontechnical review? PinmemberJessn28-Jul-05 2:57 
GeneralA Q about registry usage Pinmemberspamdump27-Jul-05 23:21 
Generalserver client game Pinsussni_tin_ya_da_v23-May-05 23:57 
GeneralInteresting PinmemberCoruscant15-May-05 2:55 
GeneralVery Nice!! PinmemberProxy4NT11-Apr-05 1:05 
Generali am looking for algortihm for Public/Private key PinmemberThatsAlok14-Nov-04 19:36 
GeneralRe: i am looking for algortihm for Public/Private key PinmemberJessn28-Jul-05 3:10 
GeneralRe: i am looking for algortihm for Public/Private key Pinsussthatsalok29-Jul-05 1:14 
GeneralGreat job! PinmemberJim Crafton5-Nov-04 10:18 
GeneralRe: Great job! PinprotectorRob Manderson5-Nov-04 10:46 
GeneralNice. PinmemberMikeBeard5-Nov-04 5:43 
GeneralRe: Nice. PinprotectorRob Manderson5-Nov-04 7:57 
QuestionCrypt.zip? PinmemberOne Stone5-Nov-04 2:08 
AnswerRe: Crypt.zip? PinprotectorRob Manderson5-Nov-04 5:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 5 Nov 2004
Article Copyright 2004 by Rob Manderson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid