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

A simple MFC class to encrypt data with the Crypto API

4.84/5 (14 votes)
3 Jul 2007GPL39 min read 1   6.2K  
An article showing how to use the Crypto API to make a simple MFC class that can encrypt and decrypt different types of data

Screenshot - crypto.jpg

Introduction

This article shows how to use Microsoft Cryptography to create a simple MFC class that will encrypt and decrypt data.

Microsoft's Cryptography API is very powerful, but a little cumbersome to use. If you need to quickly add security to your software with password-based encryption, then this little class will let you encrypt and decrypt many different kinds of data very quickly.

Using the code

Before I go into how the code works, here is a brief example of how you might use it. First of all, make sure you include Crypto.cpp and Crypto.h in your project. If you are going to be using the Cryptography code in many modules, include it in stdafx.h. Otherwise, just include it in the modules you will be using it in. To do any cryptography, one must first create the object and then derive a key from it. At the moment, the only way to derive a key is from a password.

C++
// Create the Crypto object, this initializes all of 
// the underlying handles etc.
CCrypto crypto;

// Ask the user for a password.
if(somePasswordDialog.DoModal() == IDOK)
{
    // Set the password for the crypto object.
    cryto.DeriveKey(somePasswordDialog.m_strPassword);

    // Now the crypto object is ready to use

    // ... some code using encryption and decryption ...
}

So, setting the Crypto object up for use is very easy. If you want to know how to make sure the user has specified the correct password, look further down the article. There's a point about it there.

When encrypting data with this object, one always passes over a reference to CByteArray. This is where the encrypted bytes will be stored. This may seem a little bit clunky, but in the end it turns out to be the quickest and easiest way. We cannot store, say, the result of an encrypted string in another string because the encrypted data can contain zeros. The resulting string would be malformed.

Still, why a byte array? The reason is that this class doesn't want to know where you want to store the data. It might be a file; it might be the clipboard; it might be the registry. All it knows is that you are going to want to store the encrypted data somewhere afterwards, so a byte array is the most convenient way to pass it around.

Similarly, you will at some stage load encrypted data from a file, the registry, the clipboard, a socket or anything really. The decrypt function will take that data in the form of a byte array. Overall, this is much better than writing a class that can encrypt directly to a file, a socket or whatever. All this class does is encrypt or decrypt data to and from byte arrays. It leaves storage up to you.

So, how to go about encrypting data then? Here's an example.

C++
// I assume that you have a crypto object here, that has been 
// constructed and has a key derived for it!
CCrypto crypto;

// Create a byte array, we will use this later on to store data.
CByteArray arBytes;

// To start off with, lets encrypt a string.
CString str = _T("What the hell am I supposed to write here?");
if(crypto.Encrypt(str, arBytes) == true)
{
    // Store the byte array in a file, the registry, whatever.
}

// Now I want to encrypt my applications document object.
CDocument* pDoc = GetDocument();

if(crypto.Encrypt(*pDoc, arBytes) == true)
{
    // Store the byte array in a file, the registry, whatever.
}   

// Finally, I want to encrypt an array of strings.
CStringArray arStrings;
arStrings.Add(_T("String One"));
arStrings.Add(_T("String Two"));
arStrings.Add(_T("String Three"));
if(crypto.Encrypt(arStrings, arBytes) == true)
{
    // Store the byte array in a file, the registry, whatever.
} 

It couldn't be easier. If you're really miffed and want to encrypt the data and dump it into a file, it only takes a couple of lines to open a CFile object and call Write on the contents of the byte array. What about decrypting? Have a look at the code below.

C++
// I assume that you have a crypto object here, that has been 
// constructed and has a key derived for it!
CCrypto crypto;

// Create a byte array, we will use this later on to store data.
CByteArray arBytes;

// ...load the contents of a file, registry key, whatever contains your 
// data to the byte array first!...
CString strDecrypred;
if(crypto.Decrypt(arBytes, strDecrypred) == true)
{
    cout << strDecrypted.GetString();
}



// Now I want to decrypt my applications document object.
CDocument* pDoc = GetDocument();
if(crypto.Decrypt(arBytes, *pDoc) == true)
{

}   

// Finally, I want to decrypt an array of strings.
CStringArray arStrings;
if(crypto.Decrypt(arBytes, arStrings) == true)
{

}

Hoody dizzle. Nice and easy. The only work you have to do is fill the arBytes variable with the encrypted data that you have retrieved from a file or whatever. These two code examples show all of the functions in the crypto object.

What can I encrypt?

I could've gone overboard here and provided functions for encrypting ints, doubles, std::strings, CFile objects and so on. However, I have deliberately limited what you can encrypt and decrypt to two types of object that cover everything we need.

  • CStrings - Such a common object, if you want to encrypt a string of any kind convert it to a CString. I prefer std::string, but this is an MFC class and CStrings compile happily in Unicode environments too.
  • Serializable CObjects - This covers document objects, containers and all sorts.

In MFC if you want something to be able to be serialized to a set of bytes, you make it serializable. Anything that you can serialize to a stream of bytes you can encrypt and decrypt. This is very useful, as you can directly encrypt application document objects, arrays, lists and anything that was designed to be serialized! Everyone knows how to make an MFC class serializable; it's very easy to do. So, if you have a complex data type that you need to encrypt, make it a CObject, using DECLARE_SERIAL and IMPLEMENT_SERIAL. Then write the serialize function and you're done. You can encrypt and decrypt to your heart's content.

What about unsigned char[]?

If you want to encrypt the contents of a buffer of bytes or something, copy the buffer into CByteArray. This is serializable and therefore you can encrypt it. I don't want to have to write a function that takes a pointer to some random area of memory and assumes that the correct length has been passed. If you're working with areas of raw memory, use CByteArray. It'll make your code safer, more readable and more efficient.

Great. Cheers. What about Win32?

I would've loved to write this class so that it can only encrypt an std::string or std::vector<unsigned char>, but hitching a ride on the MFC serialization mechanism just gives me such a large amount of functionality for free. With a couple of pages of code, I can encrypt and decrypt anything I can be bothered to write a serialize function for. It's so quick and easy that I couldn't resist. Sorry, Win32. Sorry, STL.

What does it all mean, Basil? How does it wooooork?

Have a quick gander at the code. It's only two files and only two important functions. OK, here's the gist (actually it's not the gist, it's the whole lot).

  • Construction - Initialise all of the crypto handles and all that stuff. If this fails, your object is useless. I normally hate putting initialisation of this kind in a constructor, but what the hell. If the initialisation of the Crypto API can't complete, your software is screwed anyway. Reinstall the Platform SDK.
  • Destruction - Uuuuuh, free everything above.
  • Encryption - OK, this is more interesting. For a serializable: create a CArchive from a CMemFile that the crypto object owns and uses as a buffer for its operations. Use Serialize to serialize the serializable (whew) to the archive. Call InternalEncrypt to call the API function that encrypts the contents of the memory file to the byte array. For a string: create a CArchive from a CMemFile that the crypto object owns and uses as a buffer for its operations. Use CArchive::operator << to serialize the string. Call InternalEncrypt to call the API function that encrypts the contents of the memory file to the byte array.
  • Decryption - Very similar to the above. For a serializable: use InternalDecrypt to decrypt the byte array to the memory file. Create a CArchive from the memory file. Call Serialize on the object, passing the archive. All done. Easy. For a string: use InternalDecrypt to decrypt the byte array to the memory file. Create a CArchive from the memory file. Call CArchive::operator << on the string. All done. Easy.

So, it's all very simple stuff. I use a CMemFile as a scratchpad for all encryption and decyption operations because then I can attach an archive to it nice and easily. Also, there may come a day when I want to encrypt 12 Gb of data, in which case I'll use a disk file instead.

What the code won't do

I have made this class as simple as possible and because of that there are some things it won't do, although I am planning to add certain features. This class will not:

  • Directly serialize STL types or built-in types. Sorry, you've got to make them into a byte array or a serializable object. There's a mechanism for turning objects into arrays of bytes, serialization, so use that.
  • Directly serialize to file. Sure, you can encrypt or decrypt a file with one call, but if you are encrypting or decrypting data in memory, it's up to you to open a file or save it.
  • Be polite if construction fails. If the Crypto API throws errors at me, Encrypt and Decrypt will return false. That's as good as the error reporting gets so far.
  • Handle non-password encryption mechanisms. This is just a case of providing another function along the lines of DeriveKey that takes a key file or whatever, but at the moment I'm only supporting password protection. It's very easy to add more functionality and it won't change how the internals of the code work.

Points of interest

Have a look at InternalEncrypt or InternalDecrypt. In the space of about fifteen lines, MFC has specified buffer lengths using:

  • int
  • UINT
  • DWORD
  • INT_PTR
  • DWORD_PTR
  • ULONGLONG

What a joke! STL had it right when they decided to be consistent and use size_t everywhere. In all my non-MFC code, I use size_t everywhere I specify a length, size, array index, etc. There are lots of static_casts in the code because MFC uses sizes inconsistently.

What's the right password?

If Decrypt is returning false, you're probably using the wrong password. If you want to be able to check, do this:

  • When the user specifies a password for the first time, encrypt the value of the password using the key generated by the password, i.e. call DeriveKey and then Encrypt. Save the encrypted password in the registry/data file/whatever.
  • The next time the user specifies a password, try and decrypt their original password. If decrypt works AND the decrypted password matches what the user gave you, then they have specified the correct password.

As far as I know, this is safer than encrypting some constant string with the password and checking that decrypting it gives the same string. This is because if someone finds out the string you are using as a check, then they can write a program that generates passwords, checks to see if the generated password decrypts the check string correctly, and then hack your application if it works. So encrypt the password itself.

Updates and more info

If I update the code, I'll update it on my site at the same time I update it here, so check there for updates. If you have comments or suggestions, feel free to email me.

Free stuff

My site contains free software you might like. If you like it or this code, buy me a beer, as I'm destitute. Really, really horribly poor.

History

  • 3 July, 2007 -- Original version posted

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)