Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

XResFile - Files Stored in Resources: Part 3 - Encrypted Files

4.93/5 (28 votes)
9 Jul 2007CPOL7 min read 1   1.2K  
This series of articles is a step-by-step guide to reading files stored in your program's resources. Along the way I will present some non-MFC classes to assist you in reading text, binary, zip, and even encrypted files that have been compiled into your program as resources.

Introduction

This series of articles discusses three typical requirements when working with file resources (by that I mean, files that have been compiled into your program as resources):

Part 1Text and binary files
Part 2Zip files
Part 3Encrypted files

Embedding an Encrypted File as a Resource

In Part 1, I covered some general topics about resources, and presented CResourceFile and CResourceTextFile, classes that allow you to read binary and text files that have been embedded as resources in your app.

In Part 2, I talked about embedding zip files as resources, and presented CResourceZip, a class that allows you to open and extract entries from a zip file that has been embedded as a resource in your app.

In this article, I will talk about CResourceDecrypt, a class that allows you to decrypt resources. As with the other classes in this series, the decryption works on memory buffers, so that nothing gets written to disk.

Creating the Encrypted File

I wanted to use the same zip file that I presented in Part 2, but I needed to encrypt it first. So I created a small console program encrypt to accept some command line arguments:

screenshot

Choosing an Encryption Algorithm

Now I came to the decision that I had been putting off. I wanted an encryption algorithm stronger than XOR, but I did not want a large footprint, or very slow decryption times. I tested many, but most were either too complicated to set up, or too slow, or I simply did not know if they could be trusted. It is surprising to me that there is no single Internet site that is considered the trusted authority on encryption algorithms, that would allow you to compare performance, size, and relative security, and would be able to give you some idea of what algorithm is good for what purpose.

I finally chose TEA, or Tiny Encryption Algorithm, that has been around for over ten years, and is considered to be one of the fastest, and offers moderate security. How complicated is it? Here is the entire code for TEA decryption:

C++
void TeaDecipher(const unsigned long *key,
                 const unsigned long *inbuf,
                 unsigned long *outbuf)
{
    const unsigned long delta = 0x9E3779B9;
    register unsigned long sum = 0xC6EF3720;
    register unsigned long y = inbuf[0], z = inbuf[1];
    register unsigned long n = 32;

    // sum = delta<<5, in general sum = delta * n
    while (n-- > 0)
    {
        z -= (y << 4 ^ y >> 5) + y ^ sum + key[sum >> 11 & 3];
        sum -= delta;
        y -= (z << 4 ^ z >> 5) + z ^ sum + key[sum & 3];
    }

    outbuf[0] = y;
    outbuf[1] = z;
}

Key Size and Format

After working with it for a while, the only downside I could find to using TEA was the key. TEA requires keys with a length of exactly 16 bytes. I kept trying to rationalize this, but I did not like it much, and I started to wonder if TEA was the right choice.

Then one day I was browsing a Usenet newsgroup, and came across a post that was talking about verifying data values by using 128-bit MD5 hash algorithm. I kept browsing, but a few minutes later, I thought, Hey! 128 bits is 16 bytes! So then I knew I could use keys consisting of plain text, 1 or 100 characters, and MD5 would give me the 16 bytes I needed for TEA. In the encrypt program I show above, and in the demo app, you will see MD5 called to generate the key for CResourceDecrypt.

Thanks to George Anescu for his article The FileDigest program and the C++ implementation of some Message-Digest algorithms for the MD5 code.

Working with Zip Files

For this article's demo program, I have set up two resources - one for unencrypted zip file (same as previous article), and one for encrypted zip file. Here are the actual lines taken from XResFileTest.rc:

C++
////////////////////////////////////////////////////////////
//
// BINARY
//
IDU_ZIP            BINARY  DISCARDABLE     "test_files.zip"
IDU_ENC            BINARY  DISCARDABLE     "test_files.enc"

For a detailed description of this Resource Compiler directive, please see Part 1.

The contents of the zip file is the same as shown in Part 2, so I will get right to the demo app:

screenshot

The demo app has the same three options as for the last article, but also has a new section to select the encrypted resource:

  1. You can selected the encrypted resource by checking the checkbox. You can also change the decryption key (and reset it!). The default key is what was used to encrypt the file.
  2. When the encrypted resource is selected, you will see these messages when you pick an option to perform.

The following code from the demo app shows how encrypted resources are handled:

C++
CResourceZip rz;

m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
m_List.Printf(CXListBox::Blue, CXListBox::White, 0,
    _T("=== Displaying zip entry %d ==="), m_nViewIndex);

BYTE *decryptbuf = NULL;

if (m_bEnc)
{
    int nLen = GetDecryptedBuffer(&decryptbuf);
    ASSERT(decryptbuf);

    if (decryptbuf)
    {
        m_List.Printf(CXListBox::Green, CXListBox::White, 0,
            _T("\tEncrypted resource opened OK"));
        rz.SetByteBuffer(decryptbuf, nLen);
        rc = TRUE;
    }
    else
    {
        m_List.Printf(CXListBox::Red, CXListBox::White, 0,
            _T("\tFailed to decrypt %d"), m_nViewIndex);
    }
}
else
{
    // not encrypted
    m_List.Printf(CXListBox::Black, CXListBox::White, 0,
        _T("\tOpening unencrypted resource IDU_ZIP"));

    rc = rz.Open(NULL, _T("IDU_ZIP"));
}

if (rc)
{
    m_List.Printf(CXListBox::Green, CXListBox::White, 0,
        _T("\tZip resource opened OK"));

    int nCount = rz.GetCount();

    if (nCount == ZIP_ENTRIES)
    {

Here is the GetDecryptedBuffer() function:

C++
int CXResFileTestDlg::GetDecryptedBuffer(BYTE ** ppBuf)
{
    int nLen = 0;

    BYTE *decryptbuf = NULL;

    char szKey[200] = { 0 };

#ifdef _UNICODE
    // The key must be ANSI, since that's what we used to encrypt.  1
    WideCharToMultiByte(CP_ACP, 0, m_strKey, -1,
        (LPSTR)szKey, sizeof(szKey)-2, NULL, NULL);
#else
    _tcsncpy(szKey, m_strKey, sizeof(szKey)-2);
#endif

    // szKey now contains ANSI string
    // get a 16-byte key by using MD5  2
    CMD5 md5;
    md5.AddData((BYTE *) szKey, strlen(szKey));
    BYTE * pKey = md5.FinalDigest();
    ASSERT(pKey);

    m_List.Printf(CXListBox::Black, CXListBox::White, 0,
        _T("\tOpening encrypted resource IDU_ENC"));

    CResourceDecrypt rd;
    BOOL rc = rd.Open(NULL, _T("IDU_ENC"), _T("BINARY"), pKey);  3
    if (rc)
    {
        decryptbuf = rd.DetachByteBuffer();
        ASSERT(decryptbuf);

        if (ppBuf)
        {
            *ppBuf = decryptbuf;
            nLen = rd.GetLength();
        }
    }

    return nLen;
}

Here is what this code is doing:

  1. The key string (as input by user) must be converted to ANSI, since that is what was used to encrypt the file (with encrypt command-line utility - see above).
  2. The MD5 hash algorithm is used to generate 16-byte key from input key string. No matter how long the input key string, MD5 generates 16 bytes.
  3. The final step is to use CResourceDecrypt to open the encrypted resource and return a pointer to the decrypted buffer.

Summary: Reading Encrypted Files

The code presented above can be boiled down to:

C++
CResourceDecrypt rd;

BOOL rc = rd.Open(NULL, _T("IDU_ENC"), _T("BINARY"), pKey);

if (rc)
{
    int nLen = rd.GetLength();
    BYTE *decryptbuf = rd.DetachByteBuffer();

    if (decryptbuf)
    {
        CResourceZip rz;
        rz.SetByteBuffer(decryptbuf, nLen);

        int nCount = rz.GetCount();

        if (nCount != 0)
        {
            --- do something ---

        }

        delete [] decryptbuf;
    }
}

CResourceDecrypt Quick Reference

Here is a complete list of functions available in CResourceDecrypt:

C++
//   Decrypt()           Decrypt an opened encrypted resource
//   Open()              Open an encrypted resource

How to Add CResourceDecrypt to Your Project

To integrate CResourceDecrypt class into your app, do the following:

  1. You first need to add the following files to your project:
    • ResourceFile.cpp
    • ResourceFile.h
    • ResourceDecrypt.cpp
    • ResourceDecrypt.h
    • tea.cpp
    • tea.h

    Depending on the type of resource, you may also wish to include the following:

    • ResourceZip.cpp
    • ResourceZip.h
    • XString.cpp
    • XString.h
    • XUnzip.cpp
    • XUnzip.h
    • ResourceTextFile.cpp
    • ResourceTextFile.h

    And depending on how you want to set up decryption, you may also want to include:

    • MD5.cpp
    • MD5.h
  2. In Visual Studio settings, select Not using pre-compiled header for all the above .cpp files that you have included.
  3. Next, include the header file ResourceDecrypt.h in the source file where you want to use CResourceDecrypt.
  4. Now you are ready to start using CResourceDecrypt. See above for sample code.

Implementation Notes

CResourceDecrypt has been implemented using C++, without any MFC or STL. It has been compiled under both VS6 and VS2005, and has been tested on XP and Vista. It should also work on Win2000 and Win98, but has not been tested on those platforms.

Summary

I have presented a class that allows you to decrypt an encrypted resource file, and access contents of that resource via memory buffer. Using CResourceZip class I presented in Part 2, and CResourceTextFile class I presented in Part 1, you can read the text file entries - stored in an encrypted zip file - line by line, just like you can with disk files.

In Part 2 I said that "Embedding zip files as resources provides a significant improvement in terms of protecting your resources from being ripped." Now you can improve on that even further, by encrypting the zip file. Without significant expertise in cryptography, it is impossible to hack a zip file that has been encrypted with TEA.

If you do not wish to use TEA, you also have the opportunity to use another decryption algorithm, by overriding the virtual CResourceDecrypt::Decrypt() function.

The weakest link in this scheme, of course, is the key. The first thing that a hacker would do is to use the strings utility on your EXE, that would immediately reveal all strings. For example, strings in XResFileTestPt3.exe are seen here:

screenshot

There are many more strings, of course - this just shows strings longer than 10 characters. The interesting thing to note is names of all the functions. If you must use plain-text string as the key, disguising it as a function name would be one option. A second option would be to disguise plain-text string with XOR or other simple algorithm. A third option - if it is possible - is to not store the key in EXE at all, but have the user enter it when your app starts up.

The approach that I have used in this article is to use an MD5 hash, that would not show up as plain-text key. Of course, given time, a hacker could trace through code execution, and find where the key is stored. For that reason, you may want to break up MD5 hash key into several pieces, and assemble them on the fly. This would make it more difficult - although not impossible - to hack.

Finally, don't forget to use integer resource IDs - these make your app much less transparent than do string resource IDs. (See Part 1 for more details.)

Revision History

Version 1.0 — 2007 July 9

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

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