Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5382085
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类: C/C++

2008-01-31 11:56:33

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):

Text and binary files
Zip files
Part 3 Encrypted files

Embedding an Encrypted File as a Resource

In , 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 , 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 , 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 , 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:

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 . 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 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:

////////////////////////////////////////////////////////////
//
// 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 .

The contents of the zip file is the same as shown in , 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:

Collapse
    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:

Collapse
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:

    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:

//   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 , and CResourceTextFile class I presented in , you can read the text file entries - stored in an encrypted zip file - line by line, just like you can with disk files.

In 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:

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 for more details.)

阅读(1178) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~