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

Applied Crypto++: Block Ciphers

4.85/5 (50 votes)
6 Apr 2008CPOL30 min read 2   10.9K  
Encrypt data using Block Ciphers with Crypto++.

Introduction

Crypto++ offers over 25 Block Ciphers, ranging from AES to XTEA. Modern block ciphers require the choice of an algorithm, mode, feedback size, padding, and sometimes rounds. This article will show the reader how to use Crypto++'s block ciphers. Topics to be visited in this article are:

  • Background
  • Crypto++
  • Block Ciphers
  • Stream Ciphers
  • Templated Mode Object vs. External Cipher Object
    • Templated Mode Object
    • External Cipher Object
    • Practical Differences
      • Retrieving Block-size
      • Cipher Text Stealing Bug
      • BTEA
  • StreamTransformationFilter
  • Test Vectors
  • Message Padding
  • Modes of Operation
    • Feedback Size
    • Chaining or Feedback
    • Initialization Vectors
    • Cipher Block Chaining
    • Electronic Cookbook
    • Output Feedback
    • Cipher Feedback
    • Counter Mode
    • Cipher Text Stealing
  • Message Authentication Code
  • Reusing Encryption and Decryption Objects
  • Using Block Ciphers
  • Miscellaneous Samples
    • CTS - Cipher Text Stealing
    • CTR - Counter Mode
    • Block and Key Sizes
    • Cipher Text Sizes
    • Using vector<byte>
    • Transformations
    • Encryptors with MACs
  • Tables
    • Block and Key Sizes
    • Plain Text versus Cipher Text Sizes

Background

Commercial block ciphers made their debut in the mid 1970s. IBM researchers Walter Tuchman and Horst Feistel were part of the team which produced Lucifer, a 128 bit block cipher with a 128 bit key. Lucifer was patented in 1971 by IBM (US Patent 3,798,359 issued in 1974). Lucifer laid the foundation for the Data Encryption Standard, which was proposed in 1975, and standardized in 1977 through FIPS 46. In 1980, four modes of operation for DES were specified in FIPS 81. It is noteworthy that 64 bit DES (with 56 bit keys) is stronger than the 128 bit Lucifer.

Symmetric Key is also known as Shared Key Cryptography since both parties use the same secret key. This is in contrast to Asymmetric Cryptography (also known as Public Key Cryptography), where the key is split into a public and private key pair.

Symmetric Ciphers solve each of the big three problems related to security. The three are collectively referred to as CIA, or Confidentiality, Integrity, and Authentication. The Confidentiality problem is ensuring our communications are private (secrecy), and needs no further explanation. The Authenticity problem is solved since we know who else has the key. Loosely speaking, by adding a final encryption operation to the cipher, message integrity can be realized.

Shared Key encryption systems are easier to implement and faster to use. In addition, symmetric ciphers are usually designed to encrypt messages of arbitrary length. However, to use the system, both parties must be able to securely share the key. This is known as the Key Distribution Problem.

A symmetric cipher uses linear and non-linear transformation to encrypt and decrypt messages. This is in contrast to an asymmetric cipher such as RSA Cryptography or Elliptic Curve Cryptography, which use a difficult problem in mathematics to develop the strength of the Cryptosystem. Linear Transformations are operations such as rotate and bit shifts. Non-linear transformations include XOR, substitutions using a S-box, and permutations using a P-box. Linear and non-linear transformations are sometimes referred to as confusion and diffusion.

Finally, if you are visiting this article to select a Crypto++ block cipher and mode of operation, please also visit Authenticated Encryption.

Crypto++

Image 1

The samples provided use various Crypto++ Symmetric Ciphers. Crypto++ can be downloaded from Wei Dai's Crypto++ pages. For compilation and integration issues, visit Integrating Crypto++ into the Microsoft Visual C++ Environment. This article is based upon assumptions presented in the previously mentioned article. For those who are interested in other C++ Cryptographic libraries, please see Peter Gutmann's Cryptlib or Victor Shoup's NTL.

Block Ciphers

Block Ciphers operate on data in units called blocks. The block size depends on the cipher being used, but it is usually 64 or 128 bits. An exception to this rule is SHACAL-2, which uses a 256 bit block.

If we would like to encrypt data which is 64 bytes long, and we have chosen a cipher with a block size of 128 bits, the cipher will break the 64 bytes into four blocks, 128 bits each. How the blocks are encrypted is detailed in Modes of Operation.

Blocking Plain Text

Figure 1: Blocking Plain Text

Stream Ciphers

Ciphers such as Sosemanuk and Wake are designed as stream ciphers. Stream Ciphers do not require a fixed size block. Block ciphers, such as DES and AES, can be made to appear like a stream cipher if we use a Crypto++ adapter called a StreamTransformationFilter.

If you find you need a feedback size of 1-bit or 8-bits when using a block cipher, consider using a stream cipher. Finally, when using a block cipher as a stream cipher, the minimum key size still exists. So, AES would still require 16 bytes of key material.

Templated Mode Object vs. External Cipher Object

When we use symmetric ciphers, we have to choose a cipher and a mode. When we choose a mode, we can use it in one of two ways in Crypto++. The first method uses the cipher as a templated parameter. The second method uses external cipher objects. Now is a good time to point out we can encrypt using a templated mode object, and decrypt using an external cipher object.

Templated Mode Object

Samples 1 through 4 use the templated version of the block cipher. In this case, the block cipher is a templated parameter to the mode object - it holds an instance of the cipher object.

C++
CTR_Mode< AES >::Encryption encryptor;
encryptor.SetKeyWithIV( key, AES::DEFAULT_KEYLENGTH, iv );

...

This allow us to use the encryption and decryption objects without the need for any details of transformation direction.

C++
// Encryption
CTR_Mode< AES >::Encryption encryptor;
encryptor.SetKeyWithIV( key, AES::DEFAULT_KEYLENGTH, iv );
 
StreamTransformationFilter stf( encryptor, new StringSink( cipher ) );
stf.Put( (byte*)plain.c_str(), plain.size() );
stf.MessageEnd(); 

...

// Decryption
CTR_Mode< AES >::Decryption decryptor;
decryptor.SetKeyWithIV( key, AES::DEFAULT_KEYLENGTH, iv );
 
StreamTransformationFilter stf( decryptor, new StringSink( recovered ) );
stf.Put( (byte*)cipher.c_str(), cipher.size() );
stf.MessageEnd();

External Cipher Object

The second method uses an External Cipher Object. In this case, the mode object holds a reference to an external cipher. Samples 5 through 9 use the external cipher object.

C++
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Encryption encryptor( aese, iv );

...

AESDecryption aesd( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Decryption decryptor( aesd, iv );

Practical Differences

Retrieving Block-size

When using a templated mode object, the member function CipherModeBase::BlockSize() is protected. This is not an issue when using an external cipher object. The result is, the following code using a templated mode object will not compile. The simple workaround for this issue is declaring the function BlockSize() as public in CipherModeBase.

C++
byte key[ AES::DEFAULT_KEYLENGTH ];
byte  iv[ AES::BLOCKSIZE ];
 
...
 
CTR_Mode< AES >::Encryption encryptor;
encryptor.SetKeyWithIV( key, sizeof(key), iv );
 
cout << "Block Size: ";
cout << encryptor.BlockSize() << " bytes" << endl;

Cipher Text Stealing Bug

When using CTS mode and a templated mode object, the following will throw a Crypto++ exception stating, "CBC_Encryption: message is too short for ciphertext stealing" at MessageEnd(), if the plain text is too short for stealing:

C++
CBC_CTS_Mode< AES >::Encryption encryptor;
encryptor.SetKeyWithIV( key, sizeof(key), iv );
 
StreamTransformationFilter stf( encryptor, new StringSink( cipher ));
stf.Put( (byte*)plain.c_str(), plain.size() );
stf.MessageEnd();

However, when using an external cipher object, MessageEnd() will cause an access violation in memcpy.asm:

C++
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Encryption encryptor( aese, iv );
 
StreamTransformationFilter stf( encryptor, new StringSink( cipher ));
stf.Put( (byte*)plain.c_str(), plain.size() );
stf.MessageEnd();

BTEA

When BTEA is required, use Needham and Wheeler's reference implementation. This is because BTEA is a variable length block cipher, which causes more problems than it solves in Crypto++. The original 1997 implementation can be found here. The 1998 update (due to Wagner's analysis) can be found here.

StreamTransformationFilter

Regardless of how we set up the block cipher, we will almost always use a StreamTransformationFilter to encrypt and decrypt data. We do this because the filter handles buffering, blocking, and padding for us. In short, it makes it easier to use the library. The exception to this is Sample 1, since we are handling blocking and padding ourselves.

C++
StreamTransformationFilter stf( encryptor, new StringSink(cipher) );
stf.Put( (byte*)plain.c_str(), plain.size() );
stf.MessageEnd();

If all data to be encrypted is not available, we can call Put() multiple times. In this case, the filter will buffer the plain text for us. At MessageEnd(), the filter will pad the message for us as required. The std::string cipher will hold the encrypted data, 'Hello World'.

C++
StreamTransformationFilter stf( encryptor, new StringSink(cipher) );

...

stf.Put( "Hello", sizeof("Hello") );
stf.Put( " ",     sizeof(" ") );
stf.Put( "World", sizeof("World") );

...

stf.MessageEnd();

Crypto++ uses a Unix pipelining paradigm: data flows from source to a destination. So, we might encounter the following when using a StreamTransformationFilter:

C++
StringSource( PlainText, true,
  new StreamTransformationFilter(
    Encryptor,
    new StringSink( CipherText )
  ) // StreamTransformationFilter
); // StringSource

Above, we start with a StringSource, and end with a StringSink. The filters in between perform the buffering, blocking, and padding. The flow of information through the StreamTransformationFilter is depicted in Figure 2.

StreamTransformationFilter Data Flow

Figure 2: Pipelining

If we were to visualize the block diagram of a system using a StreamTransformationFilter, it would be as shown in Figure 3. Note that the BufferedTransformation argument to the StreamTransformationFilter is the Encryptor object. BufferedTransformation is the base class of the Encryptor and the Decryptor.

StreamTransformationFilter Data Flow

Figure 3: StreamTransformationFilter Data Flow

Test Vectors

All ciphers provide test vectors to exercise an implementation. If we encounter different results from Crypto++ and another library, we can usually verify the results from each library using the supplied test vectors. For example, if using Triple DES (E-D-E), we can use FIPS 800-67, Appendix B. If we are concerned with an AES implementation, FIPS 197, Appendix C would be the definitive source. Other ciphers, such as BTEA, only offer reference implementations by the authors.

Message Padding

When a message is not a multiple of the cipher's block size, ECB or CBC mode messages must be padded. The method and values of padding are a source of problem with respect to interoperability between Cryptographic libraries and APIs. As Garth Lancaster points out, if you're not aware of the particulars of padding, use the StreamTransformationFilter. In this case, the Crypto++ filter will pad for you.

PKCS #5 uses RFC 1423 as the padding system: 8-(||M|| mod 8). (Note that DES, RC2, and RC5 of PKCS #5 are 8 byte block ciphers.) The scheme produces pads which can be unambiguously removed. For example, if the message requires one pad byte, the value would be 0x01. If the message requires two pad bytes, the bytes would be 0x02, 0x02, etc.

PKCS #7 uses a more general padding scheme. Rather than a mod 8 system, PKCS #7 uses k − (l mod k), which is well defined for k < 256. In this respect, PKCS #5 is a special case of PKCS #7. Schneier and Ferguson recommend either PKCS or a scheme based on appending 0x80, and then padding the balance with 0x00. For additional methods of padding, see Using Padding in Encryption or Wiki's Block Cipher Modes of Operation.

In the first example (Sample 1) below, we use AES in ECB mode. We manually pad with the string 'Hello World' with 0x00 to the cipher's block size, which will probably break interoperability. If using a StreamTransformationFilter, Crypto++ would pad with PKCS. However, we can specify how we would like the StreamTransformationFilter to pad our message. The zero padding can be achieved as shown below:

C++
// Setup
byte key[AES::DEFAULT_KEYLENGTH] = { ... };
byte  iv[AES::BLOCKSIZE] = { ... };

// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Encryption encryptor( aese, iv );

...

StreamTransformationFilter stf( encryptor, 
  new StringSink(ciphertext), ZEROS_PADDING ); 

The StreamTransformationFilter::BlockPaddingScheme offers five selections: NO_PADDING, ZEROS_PADDING, PKCS_PADDING, ONE_AND_ZEROS_PADDING, and DEFAULT_PADDING. ONE_AND_ZEROS_PADDING is the constant to specify for Schneier and Ferguson's alternate recommendation. For simplicity, the default padding scheme will be used:

C++
// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Encryption encryptor( aese, iv );

...

StreamTransformationFilter stf( encryptor, new StringSink(ciphertext) );

Modes of Operation

Block ciphers are used with Modes of Operation. In early cryptography (circa 1980), there were four approved modes from which to chose: ECB, CBC, OFB, and CFB. These modes were standardized (among others) in FIPS 81, ANSI X3.106, and ISO/IEC 10116. Later came modes such as CTR and CTS. CTR and CTS were standardized in NIST SP800-38A (SP800-38A recognized the four modes of FIPS 81, while ISO 10116 was updated). Modes of operation specify how the output of the previous rounds are used as input to the subsequent rounds. Depending on the desired properties of the cipher, we would select different modes. For example, if we wanted a cipher which was self synchronizing, we would choose CFB. If we needed a cipher which could withstand a noisy transmission line, OFB would be chosen because it is Error Correcting Code friendly. CBC mode would be our choice if we desired 'self recovery' from bit errors.

Feedback Size

Feedback sizes are a source of interoperability issues with the Crypto++ library. For example, OpenSSL and Crypto++ interoperate well when using default AES/CFB objects since both use the same default feedback size. Crypto++ also operates well with the embedded SSL library XySSL. However, mcrypt (and Python/PHP scripts built on top of mcrypt) do not interoperate well with Crypto++ without invoking alternate Crypto++ constructors. For example, Crypto++ will default to a feedback size of 128 bits and mcrypt will default to CFB mode with a default feedback size of 8 bits. See below on why we must be careful when choosing feedback sizes.

Image 5

Yet another example of a feedback size issue is the CLR's TripleDESCryptoServiceProvider class. TripleDESCryptoServiceProvider is a three key E-D-E implementation. When instantiating the class, two default parameters are CBC modes with an 8 bit feedback size.

Depending on the mode used, different feedback sizes (sub-modes) may be available. For example, NIST 800-38A specifies four feedback sizes for the CFB mode: 1-bit, 8-bit, 64-bit, and 128-bit. To set a different feedback size, specify the byte size using an alternate constructor of the <mode>_Mode_ExternalCipher object. For example, using AES in CFB mode with a default key (16 bytes), a default block size (16 bytes), and a feedback size of 64 bits:

C++
// Setup
byte key[AES::DEFAULT_KEYLENGTH] = { ... };
byte  iv[AES::BLOCKSIZE] = { ... };

// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CFB_Mode_ExternalCipher::Encryption encryptor( aese, iv, 8 /* 64 bits */ );

...

For simplicity, a default feedback size will be used, which is usually that of the cipher's block size. So, the samples will use the following:

C++
// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CFB_Mode_ExternalCipher::Encryption encryptor( aese, iv );

Should a mode not support a chosen feedback size, Crypto++ will throw an exception similar to, "CipherModeBase: feedback size cannot be specified for this cipher mode." We might encounter this when attempting to use AES with certain mode feedback sizes, since AES places additional restrictions on Rijndael. Additionally, from modes.h, the feedback size must be less than or equal to the block size. The protected member function SetFeedbackSize() from the class CFB_Mode of CipherModeBase is shown below:

C++
void SetFeedbackSize(unsigned int feedbackSize)
{
    if (feedbackSize > BlockSize())
        throw InvalidArgument("CFB_Mode: invalid feedback size");
    m_feedbackSize = feedbackSize ? feedbackSize : BlockSize();
}

Though we can specify a feedback size, we must be aware of the implications of the choice. For example, the OFB mode uses the feedback as a mechanism to produce the key stream. If the feedback size is equal to the cipher's block size, the feedback acts as a permutation of m-bit values, where m is the block length. The average cycle length is 2m- 1. For example, if we chose AES (128 bit block size), we effectively have a cycle length of 2128- 1. If we chose a feedback size of 64 bits or 32 bits, we reduce the cycle length to 2m/2 or 264. This is not too terribly unpalatable.

However, consider the case of DES (and other 8 byte block ciphers), which have limited popularity due to legacy installations. DES has an 8 byte block size. If we reduce the feedback size, we have a keystream that cycles at 232, which is not a very big number. According to FIPS 81, NIST does not support the use of the OFB mode for any amount of feedback less than 64 bits. If you find you need a feedback size of 1-bit or 8-bits, consider using a stream cipher.

Chaining of Feedback

Most modes of operation use some sort of feedback or chaining. This method uses the output from a previous set of rounds to affect the encryption at the current set of rounds. In Figure 4, the output being fed into the next set of rounds is the cipher text, which is not always the case.

Block Chaining

Figure 4: Block Chaining

Initialization Vectors

The above method has one shortcoming: the first round can only use the key, since there is no feedback from a previous round. This is shown in Figure 5.

Initial State

Figure 5: Initial State

This problem is overcome by using an Initialization Vector. Because the initialization vector bootstraps the chaining process, its size will be the same as the output block size. Addition of the initialization vector is shown in Figure 6.

Initialization Vector

Figure 6: Initialization Vector

An initialization vector does not usually have to be a secret, but it should not be reused with the same key. A reused IV will either leak information about the plain text (CBC and CFB modes), or destroy the security of the system (OFB and CTR modes).

Initialization vector requirements can be determined from the IV_REQUIREMENT type returned from IVRequirement(). The values will be UNIQUE_IV, RANDOM_IV, UNPREDICTABLE_RANDOM_IV, INTERNALLY_GENERATED_IV, or NOT_RESYNCHRONIZABLE. The values are an enumeration, and not a bit masked compilation.

Electronic Cookbook

Electronic Cookbook (ECB) is the simplest of the methods. A message is broken into blocks, and each block is combined with the key. There is no block chaining or feedback. It is usually not desirable to use the ECB mode, since the mode leaks information.

ECB Mode

Figure 7: ECB Mode

Figure 8 shows the repetition of cipher bytes due to the repetition of plain text bytes in the ECB mode. Another common example is encrypting Tux, the mascot of the Linux kernel (see Electronic Codebook (ECB) on Wiki).

ECB Mode

Figure 8: ECB Mode Output

Cipher Block Chaining

Figure 6 is a bit too general. To refine it for describing Cipher Block Chaining (CBC), we would perform the following. In Figure 9, the IV and plain text block are combined using XOR before being fed to the encryption operation. The cipher text is then fed to the next set of rounds, replacing the initialization vector.

CBC Mode

Figure 9: CBC Mode

Cipher Feedback

Figure 10 depicts the Cipher Feedback (CFB) mode. Note that in Figure 10, we encrypt the Key and Initialization Vector. Then, the output is XOR'd with the plain text to produce the cipher text. The cipher text is then fed to the next set of rounds in place of the initialization vector.

CFB Mode

Figure 10: CBC Mode

Output Feedback

Figure 11 shows the Output Feedback (OFB) mode. In this mode, the output of the encryption is tapped before the final XOR. The tapped output is fed to the next set of rounds, while the final XOR produces the cipher text. Because the output is tapped before the final XOR, the keystream can be pre-computed without the need for the plain text or cipher text. Neither the plain text nor the cipher text is used in the feedback mechanism.

OFB Mode

Figure 11: OFB Mode

Counter Mode

Counter Mode (CTR) is similar to both ECB and OFB modes. CTR uses a running counter block in place of an Initialization Vector/feedback mechanism. It is similar to the ECB mode in that each block of plain text is encrypted independently, rather than with the results of a previous set of rounds. It is similar to the OFB mode in that the Key and Counter are encrypted, and then the result is XOR'd with the plain text. The visual representation is presented in Figure 12.

OFB Mode

Figure 12: CTR Mode

As with an initialization vector, the counter is the size of the cipher's block size. In the case of default AES, this would be 16 bytes. NIST Special Publication 800-38A specifies two methods for using the CTR mode. The first method uses the entire block cipher size (16 bytes in the case of AES) as a monotonically increasing value. When we inspect the Crypto++ source code (modes.cpp), we see that the library uses this method (parameter s is the cipher's block size):

C++
inline void IncrementCounterByOne(byte *inout, unsigned int s)
{
    for (int i=s-1, carry=1; i>=0 && carry; i--)
        carry = !++inout[i];
}

NIST's second method splits the counter block into two separate objects: a nonce, and a monotonically increasing counter. A nonce only has to be unique, and not necessarily random. SP 800-38A, Section B.2 does not specify how many bits are used for the nonce versus the counter value. So, if we choose AES, we could use the most significant 8 bytes as a nonce, and the least significant 8 bytes as the counter. The nonce would not change, while the counter would increase for each 16 bytes of plain text consumed. In practice, if our counter starts at 0, we will probably never exhaust the 264 values of the counter. In either case, as with an initialization vector, we must not reuse a counter block. Finally, an initial counter block of 0 is acceptable, since the value must only be unique and not random. See CTR Mode/Initial Counter (NIST SP800-38A).

Cipher Text Stealing

Cipher Text Stealing (CTS) is a technique which forgoes padding the last block to the cipher's block size. When stealing cipher text, the following three steps are performed; the stealing occurs at step two:

Cipher Text Stealing

Original PlainText1 and PlainText2

Cipher Text StealingPad PlainText2 with low order bits from PlainText1
Cipher Text Stealing

Swap PlainText1 and PlainText2

Cipher Text StealingTruncate PlainText1

To use CTS mode, the plain text must be larger than the block size. So, if we are using DES (with a block size of 8 bytes), we can use the CTS mode for 12 bytes of plain text. If there are 7 bytes of plain text, we could not use DES (or we would have to pad the plain text). We could not chose TwoFish (with a block size of 16 bytes), since there is no previous block from which to steal. TwoFish would be a viable option if there were 17 bytes of plain text.

The output from the CTS mode is demonstrated in Sample 5. At the end of the article, Table 3 uses hyphens to indicate plain text lengths which are too small for the CTS to process.

Message Authentication Code

MACs were developed in the 1970s for the financial industry. ANSI X9.9 describes the MAC as an 8 digit hexadecimal number which is the result of passing financial messages through an authentication algorithm using a specific key.

Modes of Operation allow us to encrypt the data based on a shared key. It solves the problems of Confidentiality and Authenticity. A block cipher can also be used to solve the problem of Integrity. This is the problem of determining if the data was altered on disk or in transit. To solve this problem with block ciphers, we need to revisit Block Chaining.

We've already seen that to bootstrap the chaining process, we must supply an Initialization Vector. The cipher then grinds though the data, blocking until all the data is consumed. After the data is consumed, we have an extra output block that is not fed into the next set of rounds, since there is no plain text for the next set of rounds. This creates a unique residue.

Unique Residue

Figure 13: Unique Residue

If we combine the residue with the key (Figure 14), we have a Message Authentication Code or MAC. MACs are also known as keyed hashes. Used this way, a MAC is the Symmetric Cipher equivalent of a Digital Signature less Non-Repudiation. We lose non-repudiation because the secret key is shared with another.

Message Authentication Code

Figure 14: MAC

Though it may be tempting to encrypt our data (saving the cipher text) and then XOR the residue with the key to form an integrity code, we should not because the resulting integrity code is insecure. In this case, the integrity code is independent of both the plain text and the cipher text. To resolve the issue, we would need to make two passes over the data - one to encrypt the data, the other to generate the MAC - using separate keys or IVs. See 'Encryptors with MACs' and Sample 11 below for the Crypto++ implementation.

FIPS 81 specifies two MACs: CFB and CBC. CBC-MAC, which is based on DES, is a widely used algorithm to compute a message authentication code. CFB mode MACs are lesser known, and have some disadvantages compared to the CBC mode. CBC-MAC is now considered insecure for certain messages, such as those which vary in length. This has lead to the development of stronger MACs using 128 bit ciphers such as AES with a counter (RFC 3610). This is known as CCM, or Counter with CBC-MAC.

Reusing Encryption and Decryption Objects

It is not uncommon to create an object, encrypt data, and then reset the object for the next message. This situation frequently arises in prototype code, where strict IV requirements are not needed or the encryption and decryption can use the same object (due to mode choice). Additionally, manually managing the counter in CTR mode would use this technique. To accomplish a simple reset, use the Resynchronize() method.

C++
// Setup
byte key[ThreeWay::DEFAULT_KEYLENGTH] = { ... };
byte  iv[ThreeWay::BLOCKSIZE] = { ... };

// Encryption
ThreeWayEncryption twe( key, ThreeWay::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Encryption encryptor( twe, iv );
 
StreamTransformationFilter stf( encryptor, new StringSink( cipher ));
stf.Put( (byte*)plain.c_str(), plain.size() );
stf.MessageEnd();

// Reset Encryptor
//  IV reuse is dangerous!
encryptor.Resynchronize( iv );

Using Block Ciphers

Image 21

Sample 1 demonstrates the use of a block cipher in Crypto++. The first item we notice is the string 'Hello World' padded to achieve a block size of 16. A key is then initialized to a non-random value. The proper use of the library would include a pseudo random value. For reading on Crypto++'s pseudo random number generator, please see A Survey of Pseudo Random Number Generators.

C++
byte PlainText[] = {
  'H','e','l','l','o',' ',
  'W','o','r','l','d',
  0x0,0x0,0x0,0x0,0x0
};

byte key[ AES::DEFAULT_KEYLENGTH ];
::memset( key, 0x01, AES::DEFAULT_KEYLENGTH );

Next, we construct an encryption object, and then push the byte block in for encryption. Below, sizeof(PlainText) = AES::BLOCKSIZE = 16. Since we are using the ECB mode, there is no initialization vector.

C++
// Encrypt
ECB_Mode< AES >::Encryption Encryptor( key, sizeof(key) );

byte cbCipherText[AES::BLOCKSIZE];

Encryptor.ProcessData( cbCipherText, PlainText, sizeof(PlainText) );

We use ProcessData() to encrypt the plain text, since it allows us to receive the result in a single line of code. Next, we enter a DMZ, and then decrypt the cipher text. Finally, we print the results to the standard out. Again, sizeof(CipherText) = AES::BLOCKSIZE = 16.

C++
// Decrypt
ECB_Mode< AES >::Decryption Decryptor( key, sizeof(key) );
  
byte cbRecoveredText[AES::BLOCKSIZE];

Decryptor.ProcessData( cbRecoveredText, cbCipherText, sizeof(cbCipherText) );

If we did not construct our data as a multiple of AES::BLOCKSIZE (16 bytes), the program will assert in Debug builds and produce undefined results in Release builds. In the previous example, we were required to process data in multiples of the cipher's block size. This required us to pad the data. This will quickly become cumbersome, and is surely error prone. Sample 2 will rectify the situation using a StreamTransformationFilter.

C++
string PlainText =
  "Voltaire said, Prejudices are what fools use for reason";

// Encryptor
ECB_Mode< AES >::Encryption Encryptor( key, sizeof(key));

// Encryption
StringSource(
  PlainText,
  true,
  new StreamTransformationFilter(
    Encryptor,
    new StringSink( CipherText )
  ) // StreamTransformationFilter
); // StringSource

...

// Decryptor
ECB_Mode< AES >::Decryption Decryptor( key, sizeof(key) );

// Decryption
StringSource(
  CipherText,
  true,
  new StreamTransformationFilter(
    Decryptor,
    new StringSink( RecoveredText )
  ) // StreamTransformationFilter
); // StringSource

Image 22

If we look at our output, we see that AES is still being used in ECB mode. The filter internally calls the ProcessData() of the encryptor. We no longer need to worry about buffering and padding.

Sample 3 uses AES in CFB mode. Because we are using the CFB mode, the Encryptor and Decryptor will require an initialization vector. Unlike ECB and CBC modes, the cipher text size is the same size as the plain text since no padding occurs. Since we now require an initialization vector, the change to occur in this example is in the Encryptor's constructor:

C++
// Encryptor
CFB_Mode< AES >::Encryption
  Encryptor( key, sizeof(key), iv);
 
// Encryption
StringSource(
  PlainText,
  true,
  new StreamTransformationFilter(
    Encryptor,
    new StringSink( CipherText )
  ) // StreamTransformationFilter
); // StringSource 

The final example of templated mode objects, Sample 4, is courtesy of Jason Smethers. It allows us to quickly jump between ciphers and modes of operations, while wrapping the cipher in a StreamTransformationFilter.

C++
#define CIPHER_MODE CFB_Mode
...
#define CIPHER Twofish
...

// Encryptor
CIPHER_MODE< CIPHER >::Encryption
   Encryptor( key, sizeof(key), iv );

Below is the result of four sample runs, specifying different cipher/mode combinations. The Crypto++ Blowfish object is stating its minimum key size as 1 byte, even though Schneier claims the algorithm can use a key from (0 to 448) bits. This is due to Crypto++'s modular reduction in the source code: 0 % key-length would produce undefined results. We will find small errata such as this on occasions.

Image 23

Figure 15: Result of Various Sample 4 Executions

Miscellaneous Samples

CTS - Cipher Text Stealing

The CTS sample (Sample 5) is provided to test the special case of cipher text stealing. This is a special case because we must be aware when there is no previous block available from which to steal. This would occur when the message length is less than the block size. When testing CTS code, pay particular attention to messages which are less than the block size, since Crypto++ will throw an exception at MessageEnd().

C++
string plain = "Too Small";
string cipher, recovered;

// Encryption
ThreeWayEncryption twe( key, ThreeWay::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Encryption encryptor( twe, iv );
 
StreamTransformationFilter stfe( encryptor, new StringSink( cipher ));
stfe.Put( (byte*)plain.c_str(), plain.size() );
stfe.MessageEnd();           // Be Careful Here

...
 
// Decryption
ThreeWayDecryption twd( key, ThreeWay::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Decryption decryptor( twd, iv );
 
StreamTransformationFilter stfd( decryptor, new StringSink( recovered ));
stfd.Put( (byte*)cipher.c_str(), cipher.size() );
stfd.MessageEnd();

For brevity, this example uses the StreamTransformationFilter (without the StringSource as in the second and third examples). This is an alternate method of using the filter. In the previous examples, StringSource internally calls Put() and MessageEnd().

CTR - Counter Mode

Sample 10 exercises Crypto++'s counter mode. The program encrypts random values which are of greater size than the underlying cipher's block size. This forces the library to call IncrementCounterByOne(). Once the encryption operation returns, the sample then inspects the next counter value to be used.

Block and Key Sizes

Image 24

Sample 6 demonstrates creating an array of Crypto++ block ciphers, and then retrieving their Name, Block Size, and Key Lengths. The output of the program is available in Table 2. The code below demonstrates the array creation:

C++
BlockCipher* Ciphers[] =
{
   new AES::Encryption(),
   new Blowfish::Encryption(),
   new BTEA::Encryption(),
   new Camellia::Encryption(),
   ...
};

Once the array has been populated, we can query each cipher in the array for its various values. BlockCipher::IVSize() will always return 0 in this program, since the cipher object has not been paired with a mode object. This is not an issue, since we know the IV size is equal to the cipher's block size.

C++
for(int I = 0; I < COUNTOF(Ciphers); i++ )
{
   cout << Ciphers[i]->AlgorithmName();
   cout << Ciphers[i]->BlockSize();
   cout << Ciphers[i]->DefaultKeySize();
   ...
}

Cipher Text Sizes

Sample 9 is a comparison of plain text sizes versus cipher text sizes. Table 3 displays the results for plain text data of 12 bytes. This sample uses the same BlockCipher array. For each cipher in the array, an encryption is performed so we can develop the statistics. We know from Sample 5 (Table 2), that the longest default key and initialization vector we are required to provide is 32 bytes in size.

C++
const int BYTECOUNT = 32;
byte key[ BYTECOUNT ], iv[ BYTECOUNT ];
memset( key, 0x00, BYTECOUNT );
memset( iv, 0x00, BYTECOUNT );

for(int i = 0; i < COUNTOF( Ciphers ); i++ )
{
    cout << "Algorithm: " << Ciphers[i]->AlgorithmName() << endl;

    ECB_Mode_ExternalCipher::Encryption ecb( *(Ciphers[i]) );
    StreamTransformationFilter stf( ecb, new StringSink( cipher ) );
    stf.Put( (byte*)data.c_str(), data.length() );
    stf.MessageEnd();
 
    cout << "Ciphertext Size: " << cipher.size() << endl;
    ...
}

For ciphers which require an initialization vector, we perform the following. Unfortunately, we cannot use a base class pointer of Encryption objects, since the base class is templated.

C++
CBC_Mode_ExternalCipher::Encryption cbc( *(Ciphers[i]), iv );
StreamTransformationFilter stf( cbc, new StringSink( cipher ) );

stf.Put( (byte*)data.c_str(), data.length() );
stf.MessageEnd();

Using vector<byte>

The Crypto++ mailing list occasionally receives enquiries about moving data to and from a vector<byte>. The following sample is valid as long as the underlying array is contiguous. ISO 14882, Section 23.2.4 requires this for all types except boolean. Also note that the underlying vector does not use a secure memory allocation. It is available for download in Sample 8.

C++
string plain = "Hello World";
string cipher, recovered;
vector<byte> v;

// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Encryption encryptor( aese, iv );
 
StreamTransformationFilter stfaese( encryptor, new StringSink(scratch));
stfaese.Put( (byte*)plain.c_str(), plain.size() );
stfaese.MessageEnd(); 

// Vectorize
v.resize( cipher.size() );
StringSource( cipher, true, new ArraySink( &(v[0]), v.size() ) );

// Decryption
AESDecryption aesd( key, AES::DEFAULT_KEYLENGTH );
CBC_Mode_ExternalCipher::Decryption decryptor( aesd, iv );
 
StreamTransformationFilter stfaesd( decryptor, new StringSink( recovered ));
stfaesd.Put( &(v[0]), v.size() );
stfaesd.MessageEnd();

Transformations

When working with external cipher objects (<mode>_Mode_ExternalCipher), we have to be aware of the cipher's transformation functions, key scheduling, and interaction between rounds due to choice of modes. For example, the DES decryption operation runs the cipher in a forward direction, while reversing the key schedule. In this case, decryption is designed as a variation of the encryption function, with (at times) the design being motivated by cost (hardware savings or smaller code footprint). ECB, CBC, and CTS allow us to utilize the library as expected:

C++
// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Encryption encryptor( aese, iv );

...

// Decryption
AESDecryption aesd( key, AES::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Decryption decryptor( aesd, iv );

However, when working with OFB, CTR, or CFB modes, me must use the encryption object (in the case of AES, AESEncryption) for decryption. This is because decryption is affected by transformation direction and key scheduling.

C++
// Encryption
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CTR_Mode_ExternalCipher::Encryption encryptor( aese, iv );

...

// Decryption
AESEncryption aesd( key, AES::DEFAULT_KEYLENGTH );
CTR_Mode_ExternalCipher::Decryption decryptor( aesd, iv );

Crypto++ Assertion

If we incorrectly back out of the encryption process, we will receives a Debug assert informing us, "Assertion failed: m_cipher->IsForwardTransformation(), file c:\crypto++\modes.cpp". Sample 9 is provided to demonstrate the scenario. This leads to Table 1, which tells us which object to use since transformation direction and mode interaction are factors.

Operation/Mode

ECB

CBC

OFB

CFB

CTR

CTS

Encrypt

Encryption

Encryption

Encryption

Encryption

Encryption

Encryption

Decrypt

Decryption

Decryption

Encryption

Encryption

Encryption

Decryption

Table 1: Operation/Mode/Object Requirements

Encryptors with MACs

Using a symmetric cipher with a MAC allows us to provide both confidentiality and integrity. Crypto++ provides us with DefaultEncryptorWithMAC and DefaultDecryptorWithMAC in default.h. From the typedefs provided in default.h, the Default[En/De]cryptorWithMAC class uses triple DES (class DES_EDE2) as the block cipher in CBC mode, and SHA as the hash. The class is straightforward to use (provided in Sample 11):

C++
string message = "secret message";    
string password = "password";
string encrypted, recovered;
 
StringSource(
    message,
    true,
    new DefaultEncryptorWithMAC(
        password.c_str(),
        new StringSink( encrypted )
    ) // DefaultEncryptorWithMAC
); // StringSource
 
StringSource(
    encrypted,
    true,
    new DefaultDecryptorWithMAC(
        password.c_str(),
        new StringSink( recovered )
    ) // DefaultDecryptorWithMAC
); // StringSource
 
cout << "Recovered Text:" << endl;
cout << "  " << recovered << endl;

Default[En/De]cryptorWithMAC provides two constructors, so if we need to specify a byte[] as a passphrase and length, we can do so. In this case, our use of the StringSource would be as follows:

C++
byte password[] = 0x01, 0x02, 0x03, 0x05, 0x07, 0x11, 0x13;

StringSource(
    message,
    true,
    new DefaultEncryptorWithMAC(
        password,
        sizeof(password),
        new StringSink( encrypted )
    ) // DefaultEncryptorWithMAC
); // StringSource

If we needed an AESEncryptorWithMAC, we could do one of two things:

  • change the typedef of Default_BlockCipher from DES_EDE2 to AES
  • derive a new class (AESEncryptor and AESDecryptor) from ProxyFilter (Default[En/De]cryptor is derived from ProxyFilter) and change the default block cipher. Then, create a second pair of classes AES[En/De]cryptorWithMAC which uses AESEncryptor or AESDecryptor as the block cipher.

The implementation of the second method is fairly straightforward, since Wei provides the source code for DefaultEncryptor, DefaultDecryptor, DefaultEncryptorWithMAC, and DefaultDecryptorWithMAC. Finally, for those who are interested in encrypting a file while using a MAC, see test.cpp, functions EncryptFile() and DecryptFile().

Tables

The following tables were compiled so that the reader could compare symmetric ciphers. Sample 5 was used to produce Table 2, Sample 6 was used to produce Table 3. In Table 3, a hyphen indicates there was not enough plain text to use in the CTS (CipherText Text Stealing) mode.

Block and Key Sizes

Cipher

Block Size

Key Length

DefaultMinimumMaximum
AES16161632
Blowfish816056
Camellia16161632
CAST-128816516
CAST-25616161632
DES8888
DES-EDE28161616
DES-EDE38242424
DES-XEX38242424
GOST8323232
IDEA8161616
MARS16161656
RC28161128
RC58160255
RC616160255
SAFER-K816816
SAFER-SK816816
Serpent1616132
SHACAL-23216164
SHARK-E816116
SKIPJACK810110
3-Way1212112
Twofish1616032
XTEA816116
Table 2: Symmetric Cipher Block and Key Sizes

Plain Text versus Cipher Text Sizes

Cipher

Block
Size

Cipher Text Size

ECBCBCOFBCTRCFBCTS

AES

16

16

16

12

12

12

-

Blowfish

8

16

16

12

12

12

12

Camellia

16

16

16

12

12

12

-

CAST-128

8

16

16

12

12

12

12

CAST-256

16

16

16

12

12

12

-

DES

8

16

16

12

12

12

12

DES-EDE2

8

16

16

12

12

12

12

DES-EDE3

8

16

16

12

12

12

12

DES-XEX3

8

16

16

12

12

12

12

GOST

8

16

16

12

12

12

12

IDEA

8

16

16

12

12

12

12

MARS

16

16

16

12

12

12

-

RC2

8

16

16

12

12

12

12

RC5

8

16

16

12

12

12

12

RC6

16

16

16

12

12

12

-

SAFER-K

8

16

16

12

12

12

12

SAFER-SK

8

16

16

12

12

12

12

Serpent

16

16

16

12

12

12

-

SHACAL-2

32

32

32

12

12

12

-

SHARK-E

8

16

16

12

12

12

12

SKIPJACK

8

16

16

12

12

12

12

3-Way

12

24

24

12

12

12

-

Twofish

16

16

16

12

12

12

-

XTEA

8

16

16

12

12

12

12

Table 3: Plain Text Size (12 bytes) versus Cipher Text Size

Downloads

Acknowledgements

  • Wei Dai for Crypto++, and his invaluable help on the Crypto++ mailing list
  • Dr. A. Brooke Stephens who laid my Cryptographic foundations
  • Jason Smethers for providing sample code
  • Garth Lancaster for suggestions on completeness
  • Geoff Beier for his expertise with Crypto++ and OpenSSL
  • Stephen Schultz for his expertise with mcrypt, XySSL, and PHP/Python over mcrypt
  • Colin Bell for Crypto++ BTEA issues and workarounds

Revisions

  • 04.06.2008 Added BTEA information
  • 03.07.2008 Added Test Vector section
  • 03.07.2008 Added CLR's TripleDESCryptoServiceProvider information
  • 03.05.2008 Added Lucifer patent information
  • 02.28.2008 Added 'Practical Differences' subsection
  • 02.27.2008 Added Encryptors with MACs
  • 02.27.2008 Added Sample 11
  • 02.22.2008 Added additional information to MAC section
  • 02.20.2008 Added FIPS, ANSI, and ISO/IEC references
  • 02.13.2008 Added CTR mode
  • 02.13.2008 Added Sample 10
  • 02.13.2008 Revised Sample 1
  • 02.12.2008 Added StreamTransformationFilter section
  • 02.10.2008 Added mode feedback size and interoperability
  • 02.05.2008 Added message padding and interoperability
  • 02.04.2008 Added Round Transformation information
  • 12.06.2007 Added Samples 8 and 9
  • 12.05.2007 Initial release

Checksums

  • Sample1.zip

    MD5: 8FFDDDC6B2BDB69F66BE3647B5102C4C
    SHA-1: 96F92D4B82798A92DF58B84CA258EE1E5E66923A

  • Sample2.zip

    MD5: C3ABD80F4082CB6FE7A6327CDCDC04E1
    SHA-1: 378135B0B1FF55D9AF51E3EBFB510315CD1DD925

  • Sample3.zip

    MD5: FA3AC7A62BFFA5CD68B07FCA9D5E1D18
    SHA-1: 8E24795FC5FB1DB7C99479B4B6DD270DB9E84913

  • Sample4.zip

    MD5: 5F6388B7965D557FB6F3A0DF79DB13B9
    SHA-1: ED3F2632257DE0207C5B6764F052BF15385E7370

  • Sample5.zip

    MD5: E04592E195963805FADAF6AF31B272C3
    SHA-1: F3338BBA10514923DAA5ED45A4EE79369C45353A

  • Sample6.zip

    MD5: B370AC54D4DA4E776872ED1D56870A4F
    SHA-1: 9C9EDBDD17138A0855AF98E58BA4C804A5C89E0D

  • Sample7.zip

    MD5: D3BB63B47FCED48A737D96CDABDB4CA3
    SHA-1: B883805F50E892C4228675E21AEE2D62479A812E

  • Sample8.zip

    MD5: 3356A2E2411C217493D6A807E18DA29E
    SHA-1: B5D2A28367B3CD861995E0D0E4AD8ABBF6A102AB

  • Sample9.zip

    MD5: 4705CA41248D78A2523ACB267A71FA9F
    SHA-1: 8B9C30E311F891EF2DCCA2586313CEB43848B3CD

  • Sample10.zip

    MD5: 82EA0F9E9DDED57C64B73C52CA207C8B
    SHA-1: 4F2ACA7B999F745627332B475EF9B57043EA3732

  • Sample11.zip

    MD5: D2B44A14A0B4AEE0DC762C1715851DBE
    SHA-1: B01A05FCC384D2AB833A6B5D12D30FE886A98681

License

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