Brief Introduction to Crypto++

At some point in your life as a software engineer you will have to write code involving cryptography (crypto for short). You might need to implement a password authentication system by keeping salted password hashes for your users or add encryption for your custom binary protocol implementation or save some files securely on disk, etc. This is usually harder than it sounds and you will have to use some crypto library to get the job done.

In this post I want to write a really quick and short introduction to Crypto++, a C++ cryptographic library. My first experience with it led a lot of questions. Reading the documentation and googling answers took me some time so I want to share my experience. Hopefully you will find the information in this post helpful and will save you some time.

First things first - why using a crypto library is important

I want to stress something very important before talking about Crypto++. When dealing with cryptography it is mandatory to use standardised primitives which are implemented by engineers experienced in cryptography and reviewed by real security professionals. Writing crypto code is hard and error prone. A small miss might make your algorithm completely insecure. Even the smallest details matter a lot.

Imagine that your encrypted data consists of a header and a payload. You decrypt the header, do some sanity checks and then decrypt the payload. Now imagine that you return error immediately if you see that the header is garbage after decrypting it. Why bother with the payload after if the header is already unusable? If you do this you make your implementation vulnerable to a timing attack. An attacker can measure how fast your code return an error and deploy a brute force attack. If this sounds absurd to you read about the timing attack on Keyczar library. Similar approach was used there.

So one more time - please never ever implement (or even worse - design) your own cryptographic algorithms! Use proven libraries, developed and tested by professionals.

I want to make one final warning and I promise I'll stop being annoying. Cryptography is a complicated domain and unfortunately any errors there have got a high price. You know a lot of stories about broken self-made encryptions, leaked passwords and so on. Knowing how to use Crypto++ is important but it's even more important to understand how the cryptosystem you are about to use works and for what purposes it is suitable. Before using an algorithm from Crypto++ please spend some time to understand how it works and make sure it is designed for your usecase. This will make your job easier and will save you some trouble in the future.

What is Crypto++?

Crypto++ is a crypto library providing relatively modern C++ interface. Why relatively modern? You won't see things like smart pointers in its interfaces but the library has got memory management so you won't have to worry when it's safe to deallocate memory. For me this is its main advantage when compared with a typical C library like OpenSSL.

You can read more about the library on its website. You can find a user manual, a wiki and its source code on GitHub. Pay special attention to the README in the source tree. It's worth reading it.

How to use Crypto++ in your project

I don't want to go into too much details about how to install the library in this post. Everyone has its own preferred way. You can use your OS package manager, a C++ dependency tool like Conan or compile the library from source. Pick whatever works best for you.

Crypto++ hasn't got a CMake module shipped with the library. There are third party ones but in the sample code I will use a plain Makefile:

crypto++_sample: main.cpp
        gcc main.cpp -o crypto++_sample -lcrypto++

Memory management

If you start browsing the documentation of the project you will notice code samples which might look suspicious. For example here is one from HMAC wiki page:

// Pretty print key
encoded.clear();
StringSource ss1(key, key.size(), true,
    new HexEncoder(
        new StringSink(encoded)
    ) // HexEncoder
); // StringSource

My first thought when I see such code is "Who will release this memory?". The answer to this question is in the README file for the project. In the "Important Usage Notes" section it says:

If a constructor for A takes a pointer to an object B (except primitive types such as int and char), then A owns B and will delete B at A's destruction. If a constructor for A takes a reference to an object B, then the caller retains ownership of B and should not destroy it until A no longer needs it.

In nutshell - if a Crypto++ constructor has got a raw pointer parameter you shouldn't worry about memory deallocation. Note that this is not true for primitive types and references.

Pipelining

Crypto++ works in a way similar to the Unix shell pipes. The input data is obtained via Source interface, flows through one or more Filters and it is finally written to a Sink. This paradigm is explained in the Pipelining page from the Crypto++ Wiki. In nutshell a Source class wraps a buffer (or file), reads data from it, passes it to a filter and finally the output data is saved in a Sink, which also wraps a buffer or a file. There can be multiple filters chained one after another. The effect is just like a Unix shell pipe.

Input and Sink implementations are more or less similar. The most commonly used are StringSource/StringSink, VectorSource/VectorSink, FileSource/FileSink.

Filter is an interface and has got too many implementations to describe in a blog post. However you can think about it as an element which reads input, applies some processing to it and moves on. In the sample code I will use StreamTransformationFilter to encrypt/decrypt with AES.

Examples

I've created a basic application which encrypts a string with AES-256, encodes it in Base64 and then performs the reverse operation. My purpose is to give some intuition how the library works so you can build on this knowledge easily and catch up with Crypto++ faster. You can download the sample code from here. You need to install Crypto++ by yourself to compile the code.

Let's have a look at the main():

int main() {
    static constexpr size_t AES_KEY_SIZE = 256 / 8; //AES-256

    const std::string input {"This is a secret message."};
    std::vector<uint8_t> key(AES_KEY_SIZE);
    std::vector<uint8_t> iv(CryptoPP::AES::BLOCKSIZE);

    CryptoPP::BlockingRng rand;
    rand.GenerateBlock(key.data(), key.size());
    rand.GenerateBlock(iv.data(), iv.size());

    auto cipher = encrypt(input, key, iv);
    auto plain_text = decrypt(cipher, key, iv);

    if(plain_text != input) {
        std::cout << "Error: plain text doesn't match the input" << std::endl;
    }

    return 0;
}

The main() function bootstraps the test application. We will encrypt the input string with AES-256. First we create two vectors which will hold the key and iv used during encryption (lines 55-56). We use a random generator to generate them (CryptoPP::BlockingRng) on lines 58-60. After that we call encrypt(), decrypt() and hopefully we have got the same string as we passed initially for encryption.

Now let's see how the encryption is done:

std::string encrypt(const std::string& input, const std::vector<uint8_t>& key, const std::vector<uint8_t>& iv) {
    std::string cipher;

    auto aes = CryptoPP::AES::Encryption(key.data(), key.size());
    auto aes_cbc = CryptoPP::CBC_Mode_ExternalCipher::Encryption(aes, iv.data());

    CryptoPP::StringSource ss(
        input,
        true,
        new CryptoPP::StreamTransformationFilter(
            aes_cbc,
            new CryptoPP::Base64Encoder(
                new CryptoPP::StringSink(cipher)
            )
        )
    );

    return cipher;
}

We declare a string for the cipher text on line 12. This is the output of the function. Then we initialise aes and aes_cbc with instances of AES::Encryption and CBC_Mode_ExternalCipher::Encryption (lines 14-15). If this doesn't make much sense to you remember the paragraph of the post where I told you need to understand how the algorithm you need to use works. CBC stands for 'Cipher block chaining' and it's a mode of operation for AES. There are Wikipedia articles for CBC and AES here and here.

Back to our code. After we initialise the encryption algorithm we create a StringSource (line 17) and pass our input for its first parameter (line 18). The second argument (the bool on line 19) indicates that all data should be pumped into the attached transformation (the third parameter of StringSource on line 20). This is where the pipelining happens. The attached transformation is an instance of StreamTransformationFilter (we are still on line 20) which applies the encryption (aes_cbc on line 21) and passes the result to another transformation. In our case it is Base64Encoder (line 22) which accepts another (final in our case) StringSink transformation (line 23). It saves the base64 encoded output in the cipher string.

Visually this is what we achieved: Read input from string -> Encrypt with AES-256 -> Base64 encode -> Save to string. On first sight the code might look hard to read but it's very convenient when you get used to it.

Now let's see how decrypt() works.

std::string decrypt(const std::string& cipher_text, const std::vector<uint8_t>& key, const std::vector<uint8_t>& iv) {
    std::string plain_text;

    auto aes = CryptoPP::AES::Decryption(key.data(), key.size());
    auto aes_cbc = CryptoPP::CBC_Mode_ExternalCipher::Decryption(aes, iv.data());

    CryptoPP::StringSource ss(
        cipher_text,
        true,
        new CryptoPP::Base64Decoder(
            new CryptoPP::StreamTransformationFilter(
                aes_cbc,
                new CryptoPP::StringSink(plain_text)
            )
        )
    );

    return plain_text;
}

The code is very similar to the one in encrypt(). plain_text (line 32) is the output, aes and aes_cbc (lines 34 and 35) are initialised with AES::Decryption and CBC_Mode_ExternalCipher::Decryption. The StringSource on line 37 is initialised with the cipher text. The attached filters are the ones from encrypt() but in reverse order. First we have got a Base64Decoder (line 40). Then a StreamTransformationFilter (line 41) with the AES decryptor (line 42) and finally a StringSink writes the result to plain_text (line 43).

Visually: Read cipher text from string -> Base64 decode -> Decrypt with AES-256 -> Save to string.

Closing words

I had a lot of questions on my first encounter with Crypto++. I didn't know if I should deallocate the memory, the filter chaining was confusing and it was hard to find answers in the documentation. After grasping these concepts Crypto++ seemed far more logical and friendlier. I hope this post answered these questions for you and your Crypto++ journey will be smoother.

I promised to stop being annoying but I have to make a final disclaimer - obviously I am not a Crypto++ expert so please excuse any errors or inaccuracies in this post.

If you have got some more time to spend I'd love to read about your favourite articles, videos, etc dedicated to Crypto++ or Cryptography.

Thanks for reading and good luck!

Comments

Comments powered by Disqus