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++
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.
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.
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.
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();
...
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.
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
.
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:
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:
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.
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'.
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
:
StringSource( PlainText, true,
new StreamTransformationFilter(
Encryptor,
new StringSink( CipherText )
) );
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.
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.
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:
byte key[AES::DEFAULT_KEYLENGTH] = { ... };
byte iv[AES::BLOCKSIZE] = { ... };
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:
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.
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:
byte key[AES::DEFAULT_KEYLENGTH] = { ... };
byte iv[AES::BLOCKSIZE] = { ... };
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CFB_Mode_ExternalCipher::Encryption encryptor( aese, iv, 8 );
...
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:
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:
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.
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.
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.
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.
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).
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.
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.
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.
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.
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):
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:
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.
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.
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.
byte key[ThreeWay::DEFAULT_KEYLENGTH] = { ... };
byte iv[ThreeWay::BLOCKSIZE] = { ... };
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();
encryptor.Resynchronize( iv );
Using Block Ciphers
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.
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.
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.
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
.
string PlainText =
"Voltaire said, Prejudices are what fools use for reason";
ECB_Mode< AES >::Encryption Encryptor( key, sizeof(key));
StringSource(
PlainText,
true,
new StreamTransformationFilter(
Encryptor,
new StringSink( CipherText )
) );
...
ECB_Mode< AES >::Decryption Decryptor( key, sizeof(key) );
StringSource(
CipherText,
true,
new StreamTransformationFilter(
Decryptor,
new StringSink( RecoveredText )
) );
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:
CFB_Mode< AES >::Encryption
Encryptor( key, sizeof(key), iv);
StringSource(
PlainText,
true,
new StreamTransformationFilter(
Encryptor,
new StringSink( CipherText )
) );
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
.
#define CIPHER_MODE CFB_Mode
...
#define CIPHER Twofish
...
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.
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()
.
string plain = "Too Small";
string cipher, recovered;
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();
...
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
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:
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.
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.
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.
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.
string plain = "Hello World";
string cipher, recovered;
vector<byte> v;
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();
v.resize( cipher.size() );
StringSource( cipher, true, new ArraySink( &(v[0]), v.size() ) );
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:
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CBC_CTS_Mode_ExternalCipher::Encryption encryptor( aese, iv );
...
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.
AESEncryption aese( key, AES::DEFAULT_KEYLENGTH );
CTR_Mode_ExternalCipher::Encryption encryptor( aese, iv );
...
AESEncryption aesd( key, AES::DEFAULT_KEYLENGTH );
CTR_Mode_ExternalCipher::Decryption decryptor( aesd, iv );
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 typedef
s 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):
string message = "secret message";
string password = "password";
string encrypted, recovered;
StringSource(
message,
true,
new DefaultEncryptorWithMAC(
password.c_str(),
new StringSink( encrypted )
) );
StringSource(
encrypted,
true,
new DefaultDecryptorWithMAC(
password.c_str(),
new StringSink( recovered )
) );
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:
byte password[] = 0x01, 0x02, 0x03, 0x05, 0x07, 0x11, 0x13;
StringSource(
message,
true,
new DefaultEncryptorWithMAC(
password,
sizeof(password),
new StringSink( encrypted )
) );
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
|
Default | Minimum | Maximum |
AES | 16 | 16 | 16 | 32 |
Blowfish | 8 | 16 | 0 | 56 |
Camellia | 16 | 16 | 16 | 32 |
CAST-128 | 8 | 16 | 5 | 16 |
CAST-256 | 16 | 16 | 16 | 32 |
DES | 8 | 8 | 8 | 8 |
DES-EDE2 | 8 | 16 | 16 | 16 |
DES-EDE3 | 8 | 24 | 24 | 24 |
DES-XEX3 | 8 | 24 | 24 | 24 |
GOST | 8 | 32 | 32 | 32 |
IDEA | 8 | 16 | 16 | 16 |
MARS | 16 | 16 | 16 | 56 |
RC2 | 8 | 16 | 1 | 128 |
RC5 | 8 | 16 | 0 | 255 |
RC6 | 16 | 16 | 0 | 255 |
SAFER-K | 8 | 16 | 8 | 16 |
SAFER-SK | 8 | 16 | 8 | 16 |
Serpent | 16 | 16 | 1 | 32 |
SHACAL-2 | 32 | 16 | 1 | 64 |
SHARK-E | 8 | 16 | 1 | 16 |
SKIPJACK | 8 | 10 | 1 | 10 |
3-Way | 12 | 12 | 1 | 12 |
Twofish | 16 | 16 | 0 | 32 |
XTEA | 8 | 16 | 1 | 16 |
Table 2: Symmetric Cipher Block and Key Sizes
Plain Text versus Cipher Text Sizes
Cipher
| Block Size
| Cipher Text Size
|
ECB | CBC | OFB | CTR | CFB | CTS |
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